@matter/general 0.13.1-alpha.0-20250508-047aa0277 → 0.13.1-alpha.0-20250511-74ef153aa
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/log/LogFormat.js +3 -0
- package/dist/cjs/log/LogFormat.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/log/LogFormat.js +3 -0
- package/dist/esm/log/LogFormat.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/log/LogFormat.ts +3 -0
- package/src/util/FormattedText.ts +137 -124
|
@@ -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]) {
|