@matter/general 0.13.1-alpha.0-20250506-f9ad9c3d8 → 0.13.1-alpha.0-20250509-28e1567e1
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/dist/cjs/crypto/Crypto.d.ts +25 -24
- package/dist/cjs/crypto/Crypto.d.ts.map +1 -1
- package/dist/cjs/crypto/Crypto.js.map +1 -1
- package/dist/cjs/crypto/Spake2p.js +3 -3
- package/dist/cjs/crypto/Spake2p.js.map +1 -1
- package/dist/cjs/transaction/Transaction.d.ts +19 -19
- package/dist/cjs/util/FormattedText.d.ts +24 -1
- package/dist/cjs/util/FormattedText.d.ts.map +1 -1
- package/dist/cjs/util/FormattedText.js +118 -113
- package/dist/cjs/util/FormattedText.js.map +2 -2
- package/dist/esm/crypto/Crypto.d.ts +25 -24
- package/dist/esm/crypto/Crypto.d.ts.map +1 -1
- package/dist/esm/crypto/Crypto.js.map +1 -1
- package/dist/esm/crypto/Spake2p.js +3 -3
- package/dist/esm/crypto/Spake2p.js.map +1 -1
- package/dist/esm/transaction/Transaction.d.ts +19 -19
- package/dist/esm/util/FormattedText.d.ts +24 -1
- package/dist/esm/util/FormattedText.d.ts.map +1 -1
- package/dist/esm/util/FormattedText.js +118 -113
- package/dist/esm/util/FormattedText.js.map +2 -2
- package/package.json +2 -2
- package/src/crypto/Crypto.ts +27 -31
- package/src/crypto/Spake2p.ts +3 -3
- package/src/util/FormattedText.ts +137 -124
package/src/crypto/Crypto.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Boot } from "#util/Boot.js";
|
|
8
|
+
import { MaybePromise } from "#util/Promises.js";
|
|
8
9
|
import * as mod from "@noble/curves/abstract/modular";
|
|
9
10
|
import * as utils from "@noble/curves/abstract/utils";
|
|
10
11
|
import { p256 } from "@noble/curves/p256";
|
|
@@ -66,67 +67,62 @@ export abstract class Crypto {
|
|
|
66
67
|
}
|
|
67
68
|
};
|
|
68
69
|
|
|
69
|
-
abstract ecdhGeneratePublicKey(): { publicKey: Uint8Array; ecdh: any }
|
|
70
|
-
static readonly ecdhGeneratePublicKey = ()
|
|
71
|
-
Crypto.get().ecdhGeneratePublicKey();
|
|
70
|
+
abstract ecdhGeneratePublicKey(): MaybePromise<{ publicKey: Uint8Array; ecdh: any }>;
|
|
71
|
+
static readonly ecdhGeneratePublicKey = () => Crypto.get().ecdhGeneratePublicKey();
|
|
72
72
|
|
|
73
|
-
abstract ecdhGeneratePublicKeyAndSecret(peerPublicKey: Uint8Array): {
|
|
73
|
+
abstract ecdhGeneratePublicKeyAndSecret(peerPublicKey: Uint8Array): MaybePromise<{
|
|
74
74
|
publicKey: Uint8Array;
|
|
75
75
|
sharedSecret: Uint8Array;
|
|
76
|
-
}
|
|
77
|
-
static readonly ecdhGeneratePublicKeyAndSecret = (
|
|
78
|
-
peerPublicKey: Uint8Array,
|
|
79
|
-
): { publicKey: Uint8Array; sharedSecret: Uint8Array } =>
|
|
76
|
+
}>;
|
|
77
|
+
static readonly ecdhGeneratePublicKeyAndSecret = (peerPublicKey: Uint8Array) =>
|
|
80
78
|
Crypto.get().ecdhGeneratePublicKeyAndSecret(peerPublicKey);
|
|
81
79
|
|
|
82
|
-
abstract ecdhGenerateSecret(peerPublicKey: Uint8Array, ecdh: any): Uint8Array
|
|
83
|
-
static readonly ecdhGenerateSecret = (peerPublicKey: Uint8Array, ecdh: any)
|
|
80
|
+
abstract ecdhGenerateSecret(peerPublicKey: Uint8Array, ecdh: any): MaybePromise<Uint8Array>;
|
|
81
|
+
static readonly ecdhGenerateSecret = (peerPublicKey: Uint8Array, ecdh: any) =>
|
|
84
82
|
Crypto.get().ecdhGenerateSecret(peerPublicKey, ecdh);
|
|
85
83
|
|
|
86
|
-
abstract hash(data: Uint8Array | Uint8Array[]): Uint8Array
|
|
87
|
-
static readonly hash = (data: Uint8Array | Uint8Array[])
|
|
84
|
+
abstract hash(data: Uint8Array | Uint8Array[]): MaybePromise<Uint8Array>;
|
|
85
|
+
static readonly hash = (data: Uint8Array | Uint8Array[]) => Crypto.get().hash(data);
|
|
88
86
|
|
|
89
|
-
abstract pbkdf2(
|
|
90
|
-
static readonly pbkdf2 = (
|
|
87
|
+
abstract pbkdf2(
|
|
91
88
|
secret: Uint8Array,
|
|
92
89
|
salt: Uint8Array,
|
|
93
90
|
iteration: number,
|
|
94
91
|
keyLength: number,
|
|
95
|
-
):
|
|
92
|
+
): MaybePromise<Uint8Array>;
|
|
93
|
+
static readonly pbkdf2 = (secret: Uint8Array, salt: Uint8Array, iteration: number, keyLength: number) =>
|
|
94
|
+
Crypto.get().pbkdf2(secret, salt, iteration, keyLength);
|
|
96
95
|
|
|
97
|
-
abstract hkdf(secret: Uint8Array, salt: Uint8Array, info: Uint8Array, length?: number):
|
|
98
|
-
static readonly hkdf = (
|
|
99
|
-
secret
|
|
100
|
-
salt: Uint8Array,
|
|
101
|
-
info: Uint8Array,
|
|
102
|
-
length?: number,
|
|
103
|
-
): Promise<Uint8Array> => Crypto.get().hkdf(secret, salt, info, length);
|
|
96
|
+
abstract hkdf(secret: Uint8Array, salt: Uint8Array, info: Uint8Array, length?: number): MaybePromise<Uint8Array>;
|
|
97
|
+
static readonly hkdf = (secret: Uint8Array, salt: Uint8Array, info: Uint8Array, length?: number) =>
|
|
98
|
+
Crypto.get().hkdf(secret, salt, info, length);
|
|
104
99
|
|
|
105
|
-
abstract hmac(key: Uint8Array, data: Uint8Array): Uint8Array
|
|
106
|
-
static readonly hmac = (key: Uint8Array, data: Uint8Array)
|
|
100
|
+
abstract hmac(key: Uint8Array, data: Uint8Array): MaybePromise<Uint8Array>;
|
|
101
|
+
static readonly hmac = (key: Uint8Array, data: Uint8Array) => Crypto.get().hmac(key, data);
|
|
107
102
|
|
|
108
|
-
abstract sign(
|
|
109
|
-
static readonly sign = (
|
|
103
|
+
abstract sign(
|
|
110
104
|
privateKey: JsonWebKey,
|
|
111
105
|
data: Uint8Array | Uint8Array[],
|
|
112
106
|
dsaEncoding?: CryptoDsaEncoding,
|
|
113
|
-
): Uint8Array
|
|
107
|
+
): MaybePromise<Uint8Array>;
|
|
108
|
+
static readonly sign = (privateKey: JsonWebKey, data: Uint8Array | Uint8Array[], dsaEncoding?: CryptoDsaEncoding) =>
|
|
109
|
+
Crypto.get().sign(privateKey, data, dsaEncoding);
|
|
114
110
|
|
|
115
111
|
abstract verify(
|
|
116
112
|
publicKey: JsonWebKey,
|
|
117
113
|
data: Uint8Array,
|
|
118
114
|
signature: Uint8Array,
|
|
119
115
|
dsaEncoding?: CryptoDsaEncoding,
|
|
120
|
-
): void
|
|
116
|
+
): MaybePromise<void>;
|
|
121
117
|
static readonly verify = (
|
|
122
118
|
publicKey: JsonWebKey,
|
|
123
119
|
data: Uint8Array,
|
|
124
120
|
signature: Uint8Array,
|
|
125
121
|
dsaEncoding?: CryptoDsaEncoding,
|
|
126
|
-
)
|
|
122
|
+
) => Crypto.get().verify(publicKey, data, signature, dsaEncoding);
|
|
127
123
|
|
|
128
|
-
abstract createKeyPair(): PrivateKey
|
|
129
|
-
static readonly createKeyPair = ()
|
|
124
|
+
abstract createKeyPair(): MaybePromise<PrivateKey>;
|
|
125
|
+
static readonly createKeyPair = () => Crypto.get().createKeyPair();
|
|
130
126
|
}
|
|
131
127
|
|
|
132
128
|
Boot.init(() => {
|
package/src/crypto/Spake2p.ts
CHANGED
|
@@ -92,7 +92,7 @@ export class Spake2p {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
private async computeSecretAndVerifiers(X: Uint8Array, Y: Uint8Array, Z: Uint8Array, V: Uint8Array) {
|
|
95
|
-
const TT_HASH = this.computeTranscriptHash(X, Y, Z, V);
|
|
95
|
+
const TT_HASH = await this.computeTranscriptHash(X, Y, Z, V);
|
|
96
96
|
const Ka = TT_HASH.slice(0, 16);
|
|
97
97
|
const Ke = TT_HASH.slice(16, 32);
|
|
98
98
|
|
|
@@ -100,8 +100,8 @@ export class Spake2p {
|
|
|
100
100
|
const KcA = KcAB.slice(0, 16);
|
|
101
101
|
const KcB = KcAB.slice(16, 32);
|
|
102
102
|
|
|
103
|
-
const hAY = Crypto.hmac(KcA, Y);
|
|
104
|
-
const hBX = Crypto.hmac(KcB, X);
|
|
103
|
+
const hAY = await Crypto.hmac(KcA, Y);
|
|
104
|
+
const hBX = await Crypto.hmac(KcB, X);
|
|
105
105
|
|
|
106
106
|
return { Ke, hAY, hBX };
|
|
107
107
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const INDENT = " ";
|
|
8
8
|
|
|
9
9
|
export { camelize, describeList, serialize } from "./String.js";
|
|
10
10
|
|
|
@@ -12,14 +12,19 @@ export { camelize, describeList, serialize } from "./String.js";
|
|
|
12
12
|
* Performs word wrap. Input is assumed to be a series of paragraphs separated by a newline. Output is an array of
|
|
13
13
|
* formatted lines.
|
|
14
14
|
*
|
|
15
|
-
* Contains specialized support for lists, ESDoc directives
|
|
15
|
+
* Contains specialized support for lists, ESDoc directives and ANSI escape codes.
|
|
16
16
|
*/
|
|
17
17
|
export function FormattedText(text: string, width = 120) {
|
|
18
18
|
const structure = detectStructure(text);
|
|
19
|
-
return
|
|
19
|
+
return formatBlock(structure, width);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Types of things we consider "blocks". Most blocks are lists but we also support markdown-style quotes prefixed with
|
|
24
|
+
* ">".
|
|
25
|
+
*/
|
|
26
|
+
export enum BlockKind {
|
|
27
|
+
Simple = "simple",
|
|
23
28
|
Bullet1 = "•",
|
|
24
29
|
Bullet2 = "◦",
|
|
25
30
|
Bullet3 = "▪",
|
|
@@ -28,6 +33,7 @@ enum ListType {
|
|
|
28
33
|
Bullet6 = "‣",
|
|
29
34
|
Bullet7 = "⁃",
|
|
30
35
|
Bullet8 = "◘",
|
|
36
|
+
Quote = ">",
|
|
31
37
|
Number = "number",
|
|
32
38
|
LowerAlpha = "alpha",
|
|
33
39
|
UpperAlpha = "ALPHA",
|
|
@@ -35,115 +41,114 @@ enum ListType {
|
|
|
35
41
|
UpperRoman = "ROMAN",
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (existing == -1) {
|
|
42
|
-
listState.push(listType);
|
|
43
|
-
} else {
|
|
44
|
-
listState.length = existing + 1;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
for (const value of Object.values(ListType)) {
|
|
49
|
-
if (text[0] === value && text[1] === " ") {
|
|
50
|
-
enterList(text[0] as ListType);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function detectEnumeration(test: RegExp, listType: ListType, first: string) {
|
|
56
|
-
if (!text.match(test)) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (listState.indexOf(listType) != -1 || text.startsWith(`${first}.`)) {
|
|
61
|
-
enterList(listType);
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
44
|
+
export const Bullets = Object.entries(BlockKind)
|
|
45
|
+
.filter(([key]) => key.startsWith("Bullet"))
|
|
46
|
+
.map(([, value]) => value);
|
|
67
47
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (detectEnumeration(/^[IVX]+\./, ListType.UpperRoman, "I")) return;
|
|
71
|
-
if (detectEnumeration(/^[a-z]+\./, ListType.LowerAlpha, "a")) return;
|
|
72
|
-
if (detectEnumeration(/^[A-Z]+\./, ListType.UpperAlpha, "A")) return;
|
|
48
|
+
const enumTest = "(?:\\d+|[ivx]+|[a-z])\\.";
|
|
49
|
+
const listItemTest = new RegExp(`^(?:[${Bullets.join("")}]|${enumTest})\\s`, "i");
|
|
73
50
|
|
|
74
|
-
|
|
51
|
+
export function looksLikeListItem(text: string) {
|
|
52
|
+
return !!listItemTest.exec(text);
|
|
75
53
|
}
|
|
76
54
|
|
|
77
|
-
type
|
|
78
|
-
|
|
79
|
-
|
|
55
|
+
type Block = {
|
|
56
|
+
kind: BlockKind;
|
|
57
|
+
indentWidth: number;
|
|
58
|
+
entries: (string | Block)[];
|
|
80
59
|
};
|
|
81
60
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return { prefix: text, text: "" };
|
|
88
|
-
}
|
|
61
|
+
const Empty: Block = {
|
|
62
|
+
kind: BlockKind.Simple,
|
|
63
|
+
indentWidth: 0,
|
|
64
|
+
entries: [],
|
|
65
|
+
};
|
|
89
66
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Detect block prefixes. This is designed to handle scavenged, poorly formatted text so does not use indentation. It
|
|
69
|
+
* just focus on the prefix characters of the paragraph/line (which are the same thing as paragraphs do not include
|
|
70
|
+
* newlines).
|
|
71
|
+
*/
|
|
72
|
+
function detectBlock(text: string, breadcrumb: Block[]) {
|
|
73
|
+
const match = text.match(/^\s*(\S+)/);
|
|
74
|
+
if (!match) {
|
|
75
|
+
return;
|
|
93
76
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
77
|
+
|
|
78
|
+
const [, marker] = match;
|
|
79
|
+
|
|
80
|
+
if (Bullets.includes(marker as BlockKind) || marker === BlockKind.Quote) {
|
|
81
|
+
enterBlock(marker as BlockKind);
|
|
82
|
+
return;
|
|
97
83
|
}
|
|
98
84
|
|
|
99
|
-
|
|
100
|
-
|
|
85
|
+
if (detectEnumeration(/^\d+\.$/, "1", BlockKind.Number)) return;
|
|
86
|
+
if (detectEnumeration(/^[ivx]+\.$/, "i", BlockKind.LowerRoman)) return;
|
|
87
|
+
if (detectEnumeration(/^[IVX]+\.$/, "I", BlockKind.UpperRoman)) return;
|
|
88
|
+
if (detectEnumeration(/^[a-z]+\.$/, "a", BlockKind.LowerAlpha)) return;
|
|
89
|
+
if (detectEnumeration(/^[A-Z]+\.$/, "A", BlockKind.UpperAlpha)) return;
|
|
90
|
+
|
|
91
|
+
// Not in a block
|
|
92
|
+
breadcrumb.length = 1;
|
|
93
|
+
|
|
94
|
+
function enterBlock(kind: BlockKind) {
|
|
95
|
+
// If we are already in block of this kind, ensure it is the deepest level
|
|
96
|
+
const level = breadcrumb.findIndex(entry => entry.kind === kind);
|
|
97
|
+
if (level !== -1) {
|
|
98
|
+
breadcrumb.length = level + 1;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
// Need to start a new block
|
|
103
|
+
const block = {
|
|
104
|
+
kind,
|
|
105
|
+
indentWidth: (breadcrumb[breadcrumb.length - 1]?.indentWidth ?? 0) + kind === BlockKind.Quote ? 0 : 2,
|
|
106
106
|
entries: [],
|
|
107
|
-
}
|
|
107
|
+
};
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
breadcrumb[breadcrumb.length - 1].entries.push(block);
|
|
110
|
+
breadcrumb.push(block);
|
|
111
|
+
}
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
function detectEnumeration(test: RegExp, startsWith: string, kind: BlockKind) {
|
|
114
|
+
if (!marker.match(test)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
118
|
+
// Only consider enumeration if a.) we are already in same type of enumeration, or b.) the marker is the first
|
|
119
|
+
// element of the enumeration (e.g. "1." or "i.")
|
|
120
|
+
if (!breadcrumb.find(block => block.kind === kind)) {
|
|
121
|
+
if (marker !== `${startsWith}.`) {
|
|
122
|
+
return false;
|
|
123
123
|
}
|
|
124
|
+
}
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
enterBlock(kind);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
127
130
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Builds a block structure by detecting lists and/or quoted sections.
|
|
133
|
+
*/
|
|
134
|
+
function detectStructure(text: string): Block {
|
|
135
|
+
const lines = text.split(/\n+/).map(line => line.trimEnd());
|
|
136
|
+
if (!lines.some(p => p)) {
|
|
137
|
+
return Empty;
|
|
138
|
+
}
|
|
135
139
|
|
|
136
|
-
|
|
137
|
-
index++;
|
|
138
|
-
}
|
|
140
|
+
const breadcrumb: Block[] = [{ ...Empty, entries: [] }];
|
|
139
141
|
|
|
140
|
-
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
detectBlock(line, breadcrumb);
|
|
144
|
+
breadcrumb[breadcrumb.length - 1].entries.push(line.trim().replace(/\s+/g, " "));
|
|
141
145
|
}
|
|
142
146
|
|
|
143
|
-
return
|
|
147
|
+
return breadcrumb[0];
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
function wrapParagraph(input: string, into: string[], wrapWidth: number,
|
|
150
|
+
function wrapParagraph(input: string, into: string[], wrapWidth: number, initialPrefix: string, wrapPrefix: string) {
|
|
151
|
+
const prefixWidth = visibleWidthOf(initialPrefix);
|
|
147
152
|
const segments = input.split(/\s+/);
|
|
148
153
|
if (!segments) {
|
|
149
154
|
return;
|
|
@@ -163,50 +168,32 @@ function wrapParagraph(input: string, into: string[], wrapWidth: number, padding
|
|
|
163
168
|
}
|
|
164
169
|
}
|
|
165
170
|
|
|
166
|
-
// Configure for list prefix formatting
|
|
167
|
-
let wrapPrefix: string;
|
|
168
|
-
if (prefixWidth) {
|
|
169
|
-
// After wrapping this prefix will pad out subsequent entries
|
|
170
|
-
wrapPrefix = "".padStart(prefixWidth + 1, " ");
|
|
171
|
-
} else {
|
|
172
|
-
// No prefix
|
|
173
|
-
wrapPrefix = "";
|
|
174
|
-
}
|
|
175
|
-
|
|
176
171
|
// Wrapping setup. Track the portions of the line and current length
|
|
177
|
-
const line =
|
|
178
|
-
let
|
|
172
|
+
const line = [initialPrefix];
|
|
173
|
+
let width = prefixWidth;
|
|
179
174
|
|
|
180
175
|
// Perform actual wrapping
|
|
181
176
|
let pushedOne = false;
|
|
182
|
-
let needWrapPrefix = false;
|
|
183
177
|
for (const s of segments) {
|
|
184
|
-
const
|
|
178
|
+
const segmentWidth = visibleWidthOf(s);
|
|
185
179
|
|
|
186
180
|
// If we'll extend too far, start on a new line
|
|
187
|
-
if (
|
|
181
|
+
if (width && width + segmentWidth > wrapWidth) {
|
|
188
182
|
addLine();
|
|
189
|
-
line.length =
|
|
190
|
-
|
|
183
|
+
line.length = 0;
|
|
184
|
+
width = prefixWidth;
|
|
191
185
|
}
|
|
192
186
|
|
|
193
|
-
// Add
|
|
194
|
-
if (!line.length
|
|
195
|
-
line.push("".padStart(padding, " "));
|
|
196
|
-
length += padding;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Add wrap prefix if this is a new line in a list
|
|
200
|
-
if (needWrapPrefix) {
|
|
201
|
-
needWrapPrefix = false;
|
|
187
|
+
// Add wrap prefix if this is a new line
|
|
188
|
+
if (!line.length) {
|
|
202
189
|
line.push(wrapPrefix);
|
|
203
|
-
|
|
190
|
+
width = prefixWidth;
|
|
204
191
|
}
|
|
205
192
|
|
|
206
193
|
// Add to the line
|
|
207
194
|
line.push(s);
|
|
208
195
|
line.push(" ");
|
|
209
|
-
|
|
196
|
+
width += segmentWidth + 1;
|
|
210
197
|
}
|
|
211
198
|
|
|
212
199
|
// If there is a remaining line, add it
|
|
@@ -227,25 +214,51 @@ function wrapParagraph(input: string, into: string[], wrapWidth: number, padding
|
|
|
227
214
|
}
|
|
228
215
|
}
|
|
229
216
|
|
|
230
|
-
function
|
|
217
|
+
function separatePrefixFromContent(text: string) {
|
|
218
|
+
const match = text.match(/^(\S+\s)\s*(\S.*$)/);
|
|
219
|
+
if (match) {
|
|
220
|
+
return { prefix: match[1], text: match[2] };
|
|
221
|
+
}
|
|
222
|
+
return { prefix: "", text };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function formatBlock(block: Block, width: number) {
|
|
231
226
|
const lines = Array<string>();
|
|
232
227
|
|
|
233
|
-
function formatLevel(
|
|
234
|
-
for (const entry of
|
|
228
|
+
function formatLevel(block: Block, parentPrefix: string) {
|
|
229
|
+
for (const entry of block.entries) {
|
|
235
230
|
if (typeof entry == "string") {
|
|
236
|
-
|
|
231
|
+
let prefix, text;
|
|
232
|
+
if (block.kind === BlockKind.Simple) {
|
|
233
|
+
prefix = "";
|
|
234
|
+
text = entry;
|
|
235
|
+
} else {
|
|
236
|
+
({ prefix, text } = separatePrefixFromContent(entry));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
wrapParagraph(
|
|
240
|
+
text,
|
|
241
|
+
lines,
|
|
242
|
+
width,
|
|
243
|
+
parentPrefix + prefix,
|
|
244
|
+
parentPrefix + " ".repeat(visibleWidthOf(prefix)),
|
|
245
|
+
);
|
|
237
246
|
} else {
|
|
238
|
-
|
|
247
|
+
let childPrefix = parentPrefix;
|
|
248
|
+
if (entry.kind !== BlockKind.Quote || parentPrefix !== "") {
|
|
249
|
+
childPrefix += INDENT;
|
|
250
|
+
}
|
|
251
|
+
formatLevel(entry, childPrefix);
|
|
239
252
|
}
|
|
240
253
|
}
|
|
241
254
|
}
|
|
242
255
|
|
|
243
|
-
formatLevel(
|
|
256
|
+
formatLevel(block, "");
|
|
244
257
|
|
|
245
258
|
return lines;
|
|
246
259
|
}
|
|
247
260
|
|
|
248
|
-
function
|
|
261
|
+
function visibleWidthOf(text: string) {
|
|
249
262
|
let length = 0;
|
|
250
263
|
for (let i = 0; i < text.length; ) {
|
|
251
264
|
switch (text[i]) {
|