muggle-string 0.4.1 → 1.0.0-alpha.0
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 +32 -64
- package/out/index.d.ts +1 -9
- package/out/index.js +1 -148
- package/out/sourceRewrite.d.ts +57 -0
- package/out/sourceRewrite.js +362 -0
- package/out/types.d.ts +4 -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,40 @@
|
|
|
1
|
-
|
|
1
|
+
Ranked, conflict-free rewrites of a source text, rendered as format-neutral segments.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
muggle-string has no dependencies and knows no mapping format: a rewrite renders text and source runs to a sink, 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
|
+
- Only `core` moves text, with `cut(start, end, { data, placeholder })`. Cuts 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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
replace(s, '99', '42');
|
|
40
|
-
toString(s); // 'answer = 42'
|
|
41
|
-
|
|
42
|
-
// add string by Array method directly
|
|
43
|
-
s.unshift('var ');
|
|
44
|
-
s.push(';');
|
|
45
|
-
toString(s); // 'var answer = 42;'
|
|
46
|
-
|
|
47
|
-
for (const block of sfc.customBlocks) {
|
|
48
|
-
if (block.type === 'more-script') {
|
|
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;
|
|
20
|
+
import { createSourceRewrite, type MappedSegment, type Segment } from 'muggle-string';
|
|
21
|
+
|
|
22
|
+
const text = 'const props = defineProps<{ a: string }>();';
|
|
23
|
+
const rewrite = createSourceRewrite<string, string>('setup', text);
|
|
24
|
+
const { core } = rewrite;
|
|
25
|
+
|
|
26
|
+
const body = core.cut(0, text.length, { data: 'body' });
|
|
27
|
+
const typeArg = core.cut(text.indexOf('{'), text.lastIndexOf('}') + 1, { data: 'alias', placeholder: '__VLS_Props' });
|
|
28
|
+
core.insertBefore(0, () => ['type __VLS_Props = ', typeArg, ';\n']);
|
|
29
|
+
|
|
30
|
+
const segments: Segment<string, string>[] = [];
|
|
31
|
+
body.render({
|
|
32
|
+
text: text => segments.push(text),
|
|
33
|
+
source: (rewrite, start, end, data) => segments.push([rewrite.text.slice(start, end), rewrite.source, start, data]),
|
|
34
|
+
segment: (segment: MappedSegment<string, string>) => segments.push(segment),
|
|
35
|
+
});
|
|
36
|
+
// type __VLS_Props = { a: string };
|
|
37
|
+
// const props = defineProps<__VLS_Props>();
|
|
72
38
|
```
|
|
39
|
+
|
|
40
|
+
Code is a string, or a function, called at most once and only if the code renders, that yields strings, mapped segments, and slices.
|
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,57 @@
|
|
|
1
|
+
import type { MappedSegment, Segment } from './types';
|
|
2
|
+
/** Generated code: eager text, or a function that is called at most once, and only if the code renders. */
|
|
3
|
+
export type RewriteCode<Source, Data> = string | (() => Iterable<Segment<Source, Data> | RewriteSlice<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
|
+
/** Where a slice renders to. */
|
|
9
|
+
export interface RewriteSink<Source, Data> {
|
|
10
|
+
/** Generated text, never empty. */
|
|
11
|
+
text(text: string): void;
|
|
12
|
+
/** The kept chars `start`..`end` (never empty) of the text of `rewrite`, owned by a cut whose data is `data`. */
|
|
13
|
+
source(rewrite: SourceRewrite<Source, Data>, start: number, end: number, data: Data): void;
|
|
14
|
+
/** A mapped segment that generated code carries. */
|
|
15
|
+
segment(segment: MappedSegment<Source, Data>): void;
|
|
16
|
+
}
|
|
17
|
+
export interface RewriteWriter<Source, Data> {
|
|
18
|
+
/**
|
|
19
|
+
* `code` right before the text that starts at `offset`; it goes wherever that text goes. At the end of the text,
|
|
20
|
+
* no text starts there: the code stays after all text.
|
|
21
|
+
*/
|
|
22
|
+
insertBefore(offset: number, code: RewriteCode<Source, Data>, options?: InsertOptions): void;
|
|
23
|
+
/**
|
|
24
|
+
* `code` right after the text that ends at `offset`; it goes wherever that text goes. At 0, no text ends there: the
|
|
25
|
+
* code stays before all text.
|
|
26
|
+
*/
|
|
27
|
+
insertAfter(offset: number, code: RewriteCode<Source, Data>, options?: InsertOptions): void;
|
|
28
|
+
/** The text at `start`..`end` (not empty) is not rendered; `code` takes the place of its first char. */
|
|
29
|
+
replace(start: number, end: number, code: RewriteCode<Source, Data>, options?: InsertOptions): void;
|
|
30
|
+
}
|
|
31
|
+
export interface CoreRewriteWriter<Source, Data> extends RewriteWriter<Source, Data> {
|
|
32
|
+
/**
|
|
33
|
+
* The text at `start`..`end`, with the edits of all writers in it, rendered where the returned slice is placed,
|
|
34
|
+
* exactly once. In the cut that contains it, `placeholder` takes its place. Cuts are nested or disjoint, and of two
|
|
35
|
+
* cuts of one range the later is inside the earlier. A zero-width cut owns no char and has no placeholder; on an
|
|
36
|
+
* empty text, the first one renders the inserts.
|
|
37
|
+
*/
|
|
38
|
+
cut(start: number, end: number, options: {
|
|
39
|
+
data: Data;
|
|
40
|
+
placeholder?: RewriteCode<Source, Data>;
|
|
41
|
+
}): RewriteSlice<Source, Data>;
|
|
42
|
+
}
|
|
43
|
+
export interface RewriteSlice<Source, Data> {
|
|
44
|
+
render(sink: RewriteSink<Source, Data>): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Edits of one source text by ranked writers. Inserts attach to a side of a char, removals add up, and only core
|
|
48
|
+
* moves text, by nested cuts, so edits never conflict. The first render freezes the edits.
|
|
49
|
+
*/
|
|
50
|
+
export interface SourceRewrite<Source, Data> {
|
|
51
|
+
readonly source: Source;
|
|
52
|
+
readonly text: string;
|
|
53
|
+
readonly core: CoreRewriteWriter<Source, Data>;
|
|
54
|
+
/** The writer of `rank`, a non-negative integer. A higher rank sits outside a lower one, and core outside all. */
|
|
55
|
+
writer(rank: number): RewriteWriter<Source, Data>;
|
|
56
|
+
}
|
|
57
|
+
export declare function createSourceRewrite<Source, Data>(source: Source, text: string): SourceRewrite<Source, Data>;
|