muggle-string 0.4.1 → 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -58
- package/out/index.d.ts +1 -9
- package/out/index.js +1 -148
- package/out/sourceRewrite.d.ts +50 -0
- package/out/sourceRewrite.js +387 -0
- package/out/types.d.ts +12 -16
- package/package.json +7 -4
- package/out/base.d.ts +0 -6
- package/out/base.js +0 -119
- package/out/basic.d.ts +0 -6
- package/out/basic.js +0 -119
- package/out/binarySearch.d.ts +0 -1
- package/out/binarySearch.js +0 -25
- package/out/common.d.ts +0 -9
- package/out/common.js +0 -140
- package/out/getLength.d.ts +0 -2
- package/out/getLength.js +0 -12
- package/out/map.d.ts +0 -3
- package/out/map.js +0 -66
- package/out/overwriteSource.d.ts +0 -3
- package/out/overwriteSource.js +0 -51
- package/out/replace.d.ts +0 -3
- package/out/replace.js +0 -35
- package/out/segment.d.ts +0 -6
- package/out/segment.js +0 -39
- package/out/sourceBased.d.ts +0 -3
- package/out/sourceBased.js +0 -51
- package/out/toString.d.ts +0 -2
- package/out/toString.js +0 -8
- package/out/track.d.ts +0 -6
- package/out/track.js +0 -133
package/README.md
CHANGED
|
@@ -1,72 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
Ranked, conflict-free rewrites of a source text, rendered as format-neutral pieces.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
muggle-string has no dependencies and knows no mapping format: a rewrite renders to pieces, new text and ranges of the source, and the consumer turns them into whatever its tooling maps with.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> 1.0 replaces the segment-array helpers of 0.4 (`replace`, `replaceSourceRange`, `toString`, ...). They are gone, not deprecated.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Model
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
- Writers have ranks. A higher rank sits outside a lower one, and `core` sits outside all.
|
|
10
|
+
- `insertBefore(offset, code)` and `insertAfter(offset, code)` attach code to a side of a char, so it goes wherever that char goes. `replace(start, end, code)` removes the text and puts `code` in the place of its first char.
|
|
11
|
+
- Removals add up, so edits of different writers never conflict.
|
|
12
|
+
- Text moves only by `cut(start, end, { data, placeholder })`, which every writer has. The cuts of all writers are nested or disjoint. A cut renders its text, with the edits of all writers in it, wherever its slice is placed, exactly once; the cut around it renders `placeholder` instead.
|
|
13
|
+
- Code at the head or tail of a cut belongs to the outermost cut that has that edge.
|
|
14
|
+
- `order` sorts the entries of one writer at one side of one char; equal keys keep call order.
|
|
15
|
+
- The first render freezes the rewrite.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
```
|
|
17
|
+
## Usage
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
|
-
import {
|
|
21
|
-
toString,
|
|
22
|
-
replace,
|
|
23
|
-
} from 'muggle-string';
|
|
20
|
+
import { createSourceRewrite } from 'muggle-string';
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
version: 1,
|
|
30
|
-
resolveEmbeddedFile(fileName, sfc, embeddedFile) {
|
|
31
|
-
if (embeddedFile.fileName.replace(fileName, '').match(/^\.(js|ts|jsx|tsx)$/)) {
|
|
22
|
+
const text = 'log(name);\nconst name = "Ada";\n';
|
|
23
|
+
const rewrite = createSourceRewrite<string, string>('main.js', text);
|
|
24
|
+
const { core } = rewrite;
|
|
25
|
+
const renamer = rewrite.writer(0);
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
for (const { index } of text.matchAll(/\bname\b/g)) {
|
|
28
|
+
renamer.replace(index, index + 'name'.length, 'user');
|
|
29
|
+
}
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
const file = core.cut(0, text.length, { data: 'file' });
|
|
32
|
+
const declaration = core.cut(text.indexOf('const'), text.length, { data: 'declaration' });
|
|
33
|
+
core.insertBefore(0, declaration);
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
const pieces = file.pieces();
|
|
36
|
+
// [{ source: 'main.js', start: 11, end: 17, data: 'declaration' }, 'user', ..., { source: 'main.js', start: 0, end: 4, data: 'file' }, ...]
|
|
37
|
+
// const user = "Ada";
|
|
38
|
+
// log(user);
|
|
39
|
+
```
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
s.unshift('var ');
|
|
44
|
-
s.push(';');
|
|
45
|
-
toString(s); // 'var answer = 42;'
|
|
41
|
+
## Pieces
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
s.push([
|
|
50
|
-
block.content, // text to add
|
|
51
|
-
block.name, // source
|
|
52
|
-
0, // content offset in source
|
|
53
|
-
{
|
|
54
|
-
// language capabilities to enable in this segment
|
|
55
|
-
hover: true,
|
|
56
|
-
references: true,
|
|
57
|
-
definition: true,
|
|
58
|
-
diagnostic: true,
|
|
59
|
-
rename: true,
|
|
60
|
-
completion: true,
|
|
61
|
-
semanticTokens: true,
|
|
62
|
-
},
|
|
63
|
-
]);
|
|
64
|
-
toString(s); // 'var answer = 42;console.log( answer )'
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
};
|
|
71
|
-
module.exports = plugin;
|
|
43
|
+
```ts
|
|
44
|
+
type Piece<Source, Data> = string | { source: Source; start: number; end: number; data: Data; text?: string };
|
|
72
45
|
```
|
|
46
|
+
|
|
47
|
+
A string is new text, which maps nowhere. An object is the range `start`..`end` of the text of `source`: without `text` it copies the text there, and with `text` it is code that stands for the range, mapped as the consumer of the pieces decides. The kept text of a rewrite renders as pieces without `text`, with the data of the cut that owns them.
|
|
48
|
+
|
|
49
|
+
Code is a piece, a slice, which renders its cut in place, or an array of code. `slice.pieces(out)` appends to `out` and returns it.
|
package/out/index.d.ts
CHANGED
|
@@ -1,10 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export * from './sourceRewrite';
|
|
2
2
|
export * from './types';
|
|
3
|
-
export * from './track';
|
|
4
|
-
export declare function getLength(segments: Segment<any>[]): number;
|
|
5
|
-
export declare function toString<T extends Segment<any>>(segments: T[]): string;
|
|
6
|
-
export declare function create(source: string): Segment[];
|
|
7
|
-
export declare function replace<T extends Segment<any>>(segments: T[], pattern: string | RegExp, ...replacers: (T | ((match: string) => T))[]): void;
|
|
8
|
-
export declare function replaceAll<T extends Segment<any>>(segments: T[], pattern: RegExp, ...replacers: (T | ((match: string) => T))[]): void;
|
|
9
|
-
export declare function replaceSourceRange<T extends Segment<any>>(segments: T[], source: string | undefined, startOffset: number, endOffset: number, ...newSegments: T[]): boolean;
|
|
10
|
-
export declare function replaceRange<T extends Segment<any>>(segments: T[], startOffset: number, endOffset: number, ...newSegments: T[]): void;
|
package/out/index.js
CHANGED
|
@@ -14,153 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
|
|
18
|
-
const binarySearch_1 = require("./binarySearch");
|
|
19
|
-
const track_1 = require("./track");
|
|
17
|
+
__exportStar(require("./sourceRewrite"), exports);
|
|
20
18
|
__exportStar(require("./types"), exports);
|
|
21
|
-
__exportStar(require("./track"), exports);
|
|
22
|
-
function getLength(segments) {
|
|
23
|
-
let length = 0;
|
|
24
|
-
for (const segment of segments) {
|
|
25
|
-
length += typeof segment == 'string' ? segment.length : segment[0].length;
|
|
26
|
-
}
|
|
27
|
-
return length;
|
|
28
|
-
}
|
|
29
|
-
exports.getLength = getLength;
|
|
30
|
-
function toString(segments) {
|
|
31
|
-
return segments.map(s => typeof s === 'string' ? s : s[0]).join('');
|
|
32
|
-
}
|
|
33
|
-
exports.toString = toString;
|
|
34
|
-
function create(source) {
|
|
35
|
-
return [[source, undefined, 0]];
|
|
36
|
-
}
|
|
37
|
-
exports.create = create;
|
|
38
|
-
function replace(segments, pattern, ...replacers) {
|
|
39
|
-
const str = toString(segments);
|
|
40
|
-
const match = str.match(pattern);
|
|
41
|
-
if (match && match.index !== undefined) {
|
|
42
|
-
const startOffset = match.index;
|
|
43
|
-
const endOffset = startOffset + match[0].length;
|
|
44
|
-
(0, track_1.offsetStack)();
|
|
45
|
-
replaceRange(segments, startOffset, endOffset, ...replacers.map(replacer => typeof replacer === 'function' ? replacer(match[0]) : replacer));
|
|
46
|
-
(0, track_1.resetOffsetStack)();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
exports.replace = replace;
|
|
50
|
-
function replaceAll(segments, pattern, ...replacers) {
|
|
51
|
-
const str = toString(segments);
|
|
52
|
-
const allMatch = str.matchAll(pattern);
|
|
53
|
-
let length = str.length;
|
|
54
|
-
let lengthDiff = 0;
|
|
55
|
-
for (const match of allMatch) {
|
|
56
|
-
if (match.index !== undefined) {
|
|
57
|
-
const startOffset = match.index + lengthDiff;
|
|
58
|
-
const endOffset = startOffset + match[0].length;
|
|
59
|
-
(0, track_1.offsetStack)();
|
|
60
|
-
replaceRange(segments, startOffset, endOffset, ...replacers.map(replacer => typeof replacer === 'function' ? replacer(match[0]) : replacer));
|
|
61
|
-
(0, track_1.resetOffsetStack)();
|
|
62
|
-
const newLength = getLength(segments);
|
|
63
|
-
lengthDiff += newLength - length;
|
|
64
|
-
length = newLength;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
exports.replaceAll = replaceAll;
|
|
69
|
-
function replaceSourceRange(segments, source, startOffset, endOffset, ...newSegments) {
|
|
70
|
-
for (const segment of segments) {
|
|
71
|
-
if (typeof segment === 'string') {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (segment[1] === source) {
|
|
75
|
-
const segmentStart = segment[2];
|
|
76
|
-
const segmentEnd = segment[2] + segment[0].length;
|
|
77
|
-
if (segmentStart <= startOffset && segmentEnd >= endOffset) {
|
|
78
|
-
const inserts = [];
|
|
79
|
-
if (startOffset > segmentStart) {
|
|
80
|
-
inserts.push(trimSegmentEnd(segment, startOffset - segmentStart));
|
|
81
|
-
}
|
|
82
|
-
for (const newSegment of newSegments) {
|
|
83
|
-
inserts.push(newSegment);
|
|
84
|
-
}
|
|
85
|
-
if (endOffset < segmentEnd) {
|
|
86
|
-
inserts.push(trimSegmentStart(segment, endOffset - segmentEnd));
|
|
87
|
-
}
|
|
88
|
-
combineStrings(inserts);
|
|
89
|
-
(0, track_1.offsetStack)();
|
|
90
|
-
segments.splice(segments.indexOf(segment), 1, ...inserts);
|
|
91
|
-
(0, track_1.resetOffsetStack)();
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
exports.replaceSourceRange = replaceSourceRange;
|
|
99
|
-
function replaceRange(segments, startOffset, endOffset, ...newSegments) {
|
|
100
|
-
const offsets = toOffsets(segments);
|
|
101
|
-
const startIndex = (0, binarySearch_1.binarySearch)(offsets, startOffset);
|
|
102
|
-
const endIndex = (0, binarySearch_1.binarySearch)(offsets, endOffset);
|
|
103
|
-
const startSegment = segments[startIndex];
|
|
104
|
-
const endSegment = segments[endIndex];
|
|
105
|
-
const startSegmentStart = offsets[startIndex];
|
|
106
|
-
const endSegmentStart = offsets[endIndex];
|
|
107
|
-
const endSegmentEnd = offsets[endIndex] + (typeof endSegment === 'string' ? endSegment.length : endSegment[0].length);
|
|
108
|
-
const inserts = [];
|
|
109
|
-
if (startOffset > startSegmentStart) {
|
|
110
|
-
inserts.push(trimSegmentEnd(startSegment, startOffset - startSegmentStart));
|
|
111
|
-
}
|
|
112
|
-
for (const newSegment of newSegments) {
|
|
113
|
-
inserts.push(newSegment);
|
|
114
|
-
}
|
|
115
|
-
if (endOffset < endSegmentEnd) {
|
|
116
|
-
inserts.push(trimSegmentStart(endSegment, endOffset - endSegmentStart));
|
|
117
|
-
}
|
|
118
|
-
combineStrings(inserts);
|
|
119
|
-
(0, track_1.offsetStack)();
|
|
120
|
-
segments.splice(startIndex, endIndex - startIndex + 1, ...inserts);
|
|
121
|
-
(0, track_1.resetOffsetStack)();
|
|
122
|
-
}
|
|
123
|
-
exports.replaceRange = replaceRange;
|
|
124
|
-
function combineStrings(segments) {
|
|
125
|
-
for (let i = segments.length - 1; i >= 1; i--) {
|
|
126
|
-
if (typeof segments[i] === 'string' && typeof segments[i - 1] === 'string') {
|
|
127
|
-
segments[i - 1] = segments[i - 1] + segments[i];
|
|
128
|
-
(0, track_1.offsetStack)();
|
|
129
|
-
segments.splice(i, 1);
|
|
130
|
-
(0, track_1.resetOffsetStack)();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function trimSegmentEnd(segment, trimEnd) {
|
|
135
|
-
if (typeof segment === 'string') {
|
|
136
|
-
return segment.slice(0, trimEnd);
|
|
137
|
-
}
|
|
138
|
-
return [
|
|
139
|
-
segment[0].slice(0, trimEnd),
|
|
140
|
-
...segment.slice(1),
|
|
141
|
-
];
|
|
142
|
-
}
|
|
143
|
-
function trimSegmentStart(segment, trimStart) {
|
|
144
|
-
if (typeof segment === 'string') {
|
|
145
|
-
return segment.slice(trimStart);
|
|
146
|
-
}
|
|
147
|
-
if (trimStart < 0) {
|
|
148
|
-
trimStart += segment[0].length;
|
|
149
|
-
}
|
|
150
|
-
return [
|
|
151
|
-
segment[0].slice(trimStart),
|
|
152
|
-
segment[1],
|
|
153
|
-
segment[2] + trimStart,
|
|
154
|
-
...segment.slice(3),
|
|
155
|
-
];
|
|
156
|
-
}
|
|
157
|
-
function toOffsets(segments) {
|
|
158
|
-
const offsets = [];
|
|
159
|
-
let offset = 0;
|
|
160
|
-
for (const segment of segments) {
|
|
161
|
-
offsets.push(offset);
|
|
162
|
-
offset += typeof segment == 'string' ? segment.length : segment[0].length;
|
|
163
|
-
}
|
|
164
|
-
return offsets;
|
|
165
|
-
}
|
|
166
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Piece } from './types';
|
|
2
|
+
/** Generated code: text, pieces, and slices, which render their cut in place. */
|
|
3
|
+
export type RewriteCode<Source, Data> = Piece<Source, Data> | RewriteSlice<Source, Data> | readonly RewriteCode<Source, Data>[];
|
|
4
|
+
export interface InsertOptions {
|
|
5
|
+
/** Ascending among the entries of one writer at one side of one char; equal keys keep call order. Default 0. */
|
|
6
|
+
order?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CutOptions<Source, Data> {
|
|
9
|
+
/** The data of the kept pieces of the cut. */
|
|
10
|
+
data: Data;
|
|
11
|
+
/** The code that takes the place of the cut in the cut that contains it. */
|
|
12
|
+
placeholder?: RewriteCode<Source, Data>;
|
|
13
|
+
}
|
|
14
|
+
export interface RewriteWriter<Source, Data> {
|
|
15
|
+
/**
|
|
16
|
+
* `code` right before the text that starts at `offset`; it goes wherever that text goes. At the end of the text,
|
|
17
|
+
* no text starts there: the code stays after all text.
|
|
18
|
+
*/
|
|
19
|
+
insertBefore(offset: number, code: RewriteCode<Source, Data>, options?: InsertOptions): void;
|
|
20
|
+
/**
|
|
21
|
+
* `code` right after the text that ends at `offset`; it goes wherever that text goes. At 0, no text ends there: the
|
|
22
|
+
* code stays before all text.
|
|
23
|
+
*/
|
|
24
|
+
insertAfter(offset: number, code: RewriteCode<Source, Data>, options?: InsertOptions): void;
|
|
25
|
+
/** The text at `start`..`end` (not empty) is not rendered; `code` takes the place of its first char. */
|
|
26
|
+
replace(start: number, end: number, code: RewriteCode<Source, Data>, options?: InsertOptions): void;
|
|
27
|
+
/**
|
|
28
|
+
* The text at `start`..`end`, with the edits of all writers in it, rendered where the returned slice is placed,
|
|
29
|
+
* exactly once. In the cut that contains it, `placeholder` takes its place. The cuts of all writers are nested or
|
|
30
|
+
* disjoint, and of two cuts of one range the later is inside the earlier. A zero-width cut owns no char and has no
|
|
31
|
+
* placeholder; on an empty text, the first one renders the inserts.
|
|
32
|
+
*/
|
|
33
|
+
cut(start: number, end: number, options: CutOptions<Source, Data>): RewriteSlice<Source, Data>;
|
|
34
|
+
}
|
|
35
|
+
export interface RewriteSlice<Source, Data> {
|
|
36
|
+
/** Appends the pieces of the slice to `out`, and returns `out`. A kept piece has no `text`. */
|
|
37
|
+
pieces(out?: Piece<Source, Data>[]): Piece<Source, Data>[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Edits of one source text by ranked writers. Inserts attach to a side of a char, removals add up, and text moves
|
|
41
|
+
* only by nested cuts, so edits never conflict. The first render freezes the edits.
|
|
42
|
+
*/
|
|
43
|
+
export interface SourceRewrite<Source, Data> {
|
|
44
|
+
readonly source: Source;
|
|
45
|
+
readonly text: string;
|
|
46
|
+
readonly core: RewriteWriter<Source, Data>;
|
|
47
|
+
/** The writer of `rank`, a non-negative integer. A higher rank sits outside a lower one, and core outside all. */
|
|
48
|
+
writer(rank: number): RewriteWriter<Source, Data>;
|
|
49
|
+
}
|
|
50
|
+
export declare function createSourceRewrite<Source, Data>(source: Source, text: string): SourceRewrite<Source, Data>;
|