wyreframe 0.2.2 → 0.4.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/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.test.ts +252 -0
- package/src/index.ts +142 -0
- package/src/parser/Fixer/FixTypes.mjs +2 -0
- package/src/parser/Fixer/FixTypes.res +34 -0
- package/src/parser/Fixer/Fixer.gen.tsx +24 -0
- package/src/parser/Fixer/Fixer.mjs +447 -0
- package/src/parser/Fixer/Fixer.res +567 -0
- package/src/renderer/Renderer.gen.tsx +8 -1
- package/src/renderer/Renderer.mjs +21 -11
- package/src/renderer/Renderer.res +34 -4
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
// Fixer.res
|
|
2
|
+
// Auto-fix functionality for common wireframe errors and warnings
|
|
3
|
+
|
|
4
|
+
open FixTypes
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// String Manipulation Helpers
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Split text into lines (preserving empty lines)
|
|
12
|
+
*/
|
|
13
|
+
let splitLines = (text: string): array<string> => {
|
|
14
|
+
text->String.split("\n")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Join lines back into text
|
|
19
|
+
*/
|
|
20
|
+
let joinLines = (lines: array<string>): string => {
|
|
21
|
+
lines->Array.join("\n")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get a specific line from text (0-indexed)
|
|
26
|
+
*/
|
|
27
|
+
let getLine = (lines: array<string>, row: int): option<string> => {
|
|
28
|
+
lines->Array.get(row)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Replace a specific line in the array (0-indexed)
|
|
33
|
+
*/
|
|
34
|
+
let replaceLine = (lines: array<string>, row: int, newLine: string): array<string> => {
|
|
35
|
+
lines->Array.mapWithIndex((line, idx) => {
|
|
36
|
+
if idx === row {
|
|
37
|
+
newLine
|
|
38
|
+
} else {
|
|
39
|
+
line
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Insert characters at a specific position in a string
|
|
46
|
+
*/
|
|
47
|
+
let insertAt = (str: string, col: int, chars: string): string => {
|
|
48
|
+
let before = str->String.slice(~start=0, ~end=col)
|
|
49
|
+
let after = str->String.sliceToEnd(~start=col)
|
|
50
|
+
before ++ chars ++ after
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Remove characters at a specific position in a string
|
|
55
|
+
*/
|
|
56
|
+
let removeAt = (str: string, col: int, count: int): string => {
|
|
57
|
+
let before = str->String.slice(~start=0, ~end=col)
|
|
58
|
+
let after = str->String.sliceToEnd(~start=col + count)
|
|
59
|
+
before ++ after
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Replace a character at a specific position
|
|
64
|
+
*/
|
|
65
|
+
let replaceCharAt = (str: string, col: int, char: string): string => {
|
|
66
|
+
let before = str->String.slice(~start=0, ~end=col)
|
|
67
|
+
let after = str->String.sliceToEnd(~start=col + 1)
|
|
68
|
+
before ++ char ++ after
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Fix Strategies for Each Error Type
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fix MisalignedPipe - adjust column position of '|' character
|
|
77
|
+
*
|
|
78
|
+
* Before: | content | (pipe at wrong column)
|
|
79
|
+
* After: | content | (pipe at correct column)
|
|
80
|
+
*/
|
|
81
|
+
let fixMisalignedPipe = (text: string, error: ErrorTypes.t): option<(string, fixedIssue)> => {
|
|
82
|
+
switch error.code {
|
|
83
|
+
| MisalignedPipe({position, expectedCol, actualCol}) => {
|
|
84
|
+
let lines = splitLines(text)
|
|
85
|
+
|
|
86
|
+
switch getLine(lines, position.row) {
|
|
87
|
+
| None => None
|
|
88
|
+
| Some(line) => {
|
|
89
|
+
// Calculate how many spaces to add or remove
|
|
90
|
+
let diff = expectedCol - actualCol
|
|
91
|
+
|
|
92
|
+
let newLine = if diff > 0 {
|
|
93
|
+
// Need to add spaces before the pipe
|
|
94
|
+
insertAt(line, actualCol, String.repeat(" ", diff))
|
|
95
|
+
} else {
|
|
96
|
+
// Need to remove spaces before the pipe
|
|
97
|
+
let removeCount = Js.Math.abs_int(diff)
|
|
98
|
+
// Make sure we're removing spaces, not content
|
|
99
|
+
let beforePipe = line->String.slice(~start=actualCol + diff, ~end=actualCol)
|
|
100
|
+
if beforePipe->String.trim === "" {
|
|
101
|
+
removeAt(line, actualCol + diff, removeCount)
|
|
102
|
+
} else {
|
|
103
|
+
line // Can't safely remove non-space characters
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if newLine !== line {
|
|
108
|
+
let newLines = replaceLine(lines, position.row, newLine)
|
|
109
|
+
let fixedText = joinLines(newLines)
|
|
110
|
+
|
|
111
|
+
Some((
|
|
112
|
+
fixedText,
|
|
113
|
+
{
|
|
114
|
+
original: error,
|
|
115
|
+
description: `Aligned pipe at line ${Int.toString(position.row + 1)} to column ${Int.toString(expectedCol + 1)}`,
|
|
116
|
+
line: position.row + 1,
|
|
117
|
+
column: expectedCol + 1,
|
|
118
|
+
},
|
|
119
|
+
))
|
|
120
|
+
} else {
|
|
121
|
+
None
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
| _ => None
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Fix MisalignedClosingBorder - adjust closing '|' position
|
|
132
|
+
*/
|
|
133
|
+
let fixMisalignedClosingBorder = (text: string, error: ErrorTypes.t): option<(string, fixedIssue)> => {
|
|
134
|
+
switch error.code {
|
|
135
|
+
| MisalignedClosingBorder({position, expectedCol, actualCol}) => {
|
|
136
|
+
let lines = splitLines(text)
|
|
137
|
+
|
|
138
|
+
switch getLine(lines, position.row) {
|
|
139
|
+
| None => None
|
|
140
|
+
| Some(line) => {
|
|
141
|
+
let diff = expectedCol - actualCol
|
|
142
|
+
|
|
143
|
+
let newLine = if diff > 0 {
|
|
144
|
+
// Need to add spaces before the closing pipe
|
|
145
|
+
insertAt(line, actualCol, String.repeat(" ", diff))
|
|
146
|
+
} else {
|
|
147
|
+
// Need to remove spaces before the closing pipe
|
|
148
|
+
let removeCount = Js.Math.abs_int(diff)
|
|
149
|
+
let beforePipe = line->String.slice(~start=actualCol + diff, ~end=actualCol)
|
|
150
|
+
if beforePipe->String.trim === "" {
|
|
151
|
+
removeAt(line, actualCol + diff, removeCount)
|
|
152
|
+
} else {
|
|
153
|
+
line
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if newLine !== line {
|
|
158
|
+
let newLines = replaceLine(lines, position.row, newLine)
|
|
159
|
+
let fixedText = joinLines(newLines)
|
|
160
|
+
|
|
161
|
+
Some((
|
|
162
|
+
fixedText,
|
|
163
|
+
{
|
|
164
|
+
original: error,
|
|
165
|
+
description: `Aligned closing border at line ${Int.toString(position.row + 1)} to column ${Int.toString(expectedCol + 1)}`,
|
|
166
|
+
line: position.row + 1,
|
|
167
|
+
column: expectedCol + 1,
|
|
168
|
+
},
|
|
169
|
+
))
|
|
170
|
+
} else {
|
|
171
|
+
None
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
| _ => None
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Fix UnusualSpacing - replace tabs with spaces
|
|
182
|
+
*/
|
|
183
|
+
let fixUnusualSpacing = (text: string, error: ErrorTypes.t): option<(string, fixedIssue)> => {
|
|
184
|
+
switch error.code {
|
|
185
|
+
| UnusualSpacing({position, issue}) => {
|
|
186
|
+
// Check if the issue is about tabs
|
|
187
|
+
if issue->String.includes("tab") || issue->String.includes("Tab") {
|
|
188
|
+
let lines = splitLines(text)
|
|
189
|
+
|
|
190
|
+
switch getLine(lines, position.row) {
|
|
191
|
+
| None => None
|
|
192
|
+
| Some(line) => {
|
|
193
|
+
// Replace tabs with 2 spaces (common convention)
|
|
194
|
+
let newLine = line->String.replaceAll("\t", " ")
|
|
195
|
+
|
|
196
|
+
if newLine !== line {
|
|
197
|
+
let newLines = replaceLine(lines, position.row, newLine)
|
|
198
|
+
let fixedText = joinLines(newLines)
|
|
199
|
+
|
|
200
|
+
Some((
|
|
201
|
+
fixedText,
|
|
202
|
+
{
|
|
203
|
+
original: error,
|
|
204
|
+
description: `Replaced tabs with spaces at line ${Int.toString(position.row + 1)}`,
|
|
205
|
+
line: position.row + 1,
|
|
206
|
+
column: position.col + 1,
|
|
207
|
+
},
|
|
208
|
+
))
|
|
209
|
+
} else {
|
|
210
|
+
None
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
None
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
| _ => None
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Fix UnclosedBracket - add closing ']' at end of line
|
|
224
|
+
*/
|
|
225
|
+
let fixUnclosedBracket = (text: string, error: ErrorTypes.t): option<(string, fixedIssue)> => {
|
|
226
|
+
switch error.code {
|
|
227
|
+
| UnclosedBracket({opening}) => {
|
|
228
|
+
let lines = splitLines(text)
|
|
229
|
+
|
|
230
|
+
switch getLine(lines, opening.row) {
|
|
231
|
+
| None => None
|
|
232
|
+
| Some(line) => {
|
|
233
|
+
// Find the content after '[' and close it
|
|
234
|
+
let trimmedLine = line->String.trimEnd
|
|
235
|
+
|
|
236
|
+
// Only add ']' if the line doesn't already end with it
|
|
237
|
+
if !(trimmedLine->String.endsWith("]")) {
|
|
238
|
+
// Add ' ]' with proper spacing
|
|
239
|
+
let newLine = trimmedLine ++ " ]"
|
|
240
|
+
let newLines = replaceLine(lines, opening.row, newLine)
|
|
241
|
+
let fixedText = joinLines(newLines)
|
|
242
|
+
|
|
243
|
+
Some((
|
|
244
|
+
fixedText,
|
|
245
|
+
{
|
|
246
|
+
original: error,
|
|
247
|
+
description: `Added closing bracket at line ${Int.toString(opening.row + 1)}`,
|
|
248
|
+
line: opening.row + 1,
|
|
249
|
+
column: String.length(trimmedLine) + 2,
|
|
250
|
+
},
|
|
251
|
+
))
|
|
252
|
+
} else {
|
|
253
|
+
None
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
| _ => None
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Fix MismatchedWidth - extend the shorter border to match the longer one
|
|
264
|
+
*/
|
|
265
|
+
let fixMismatchedWidth = (text: string, error: ErrorTypes.t): option<(string, fixedIssue)> => {
|
|
266
|
+
switch error.code {
|
|
267
|
+
| MismatchedWidth({topLeft, topWidth, bottomWidth}) => {
|
|
268
|
+
let lines = splitLines(text)
|
|
269
|
+
let diff = topWidth - bottomWidth
|
|
270
|
+
|
|
271
|
+
if diff === 0 {
|
|
272
|
+
None
|
|
273
|
+
} else {
|
|
274
|
+
// Find the bottom border line
|
|
275
|
+
// We need to trace down from topLeft to find the bottom
|
|
276
|
+
let rec findBottomRow = (row: int): option<int> => {
|
|
277
|
+
if row >= Array.length(lines) {
|
|
278
|
+
None
|
|
279
|
+
} else {
|
|
280
|
+
switch getLine(lines, row) {
|
|
281
|
+
| None => None
|
|
282
|
+
| Some(line) => {
|
|
283
|
+
// Check if this line has a '+' at the same column as topLeft
|
|
284
|
+
let col = topLeft.col
|
|
285
|
+
if col < String.length(line) {
|
|
286
|
+
let char = line->String.charAt(col)
|
|
287
|
+
if char === "+" && row > topLeft.row {
|
|
288
|
+
Some(row)
|
|
289
|
+
} else {
|
|
290
|
+
findBottomRow(row + 1)
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
findBottomRow(row + 1)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
switch findBottomRow(topLeft.row + 1) {
|
|
301
|
+
| None => None
|
|
302
|
+
| Some(bottomRow) => {
|
|
303
|
+
switch getLine(lines, bottomRow) {
|
|
304
|
+
| None => None
|
|
305
|
+
| Some(bottomLine) => {
|
|
306
|
+
if diff > 0 {
|
|
307
|
+
// Bottom is shorter, need to extend it
|
|
308
|
+
// Find the closing '+' and add dashes before it
|
|
309
|
+
let closingPlusCol = topLeft.col + bottomWidth - 1
|
|
310
|
+
if closingPlusCol >= 0 && closingPlusCol < String.length(bottomLine) {
|
|
311
|
+
let before = bottomLine->String.slice(~start=0, ~end=closingPlusCol)
|
|
312
|
+
let after = bottomLine->String.sliceToEnd(~start=closingPlusCol)
|
|
313
|
+
let dashes = String.repeat("-", diff)
|
|
314
|
+
let newLine = before ++ dashes ++ after
|
|
315
|
+
|
|
316
|
+
let newLines = replaceLine(lines, bottomRow, newLine)
|
|
317
|
+
let fixedText = joinLines(newLines)
|
|
318
|
+
|
|
319
|
+
Some((
|
|
320
|
+
fixedText,
|
|
321
|
+
{
|
|
322
|
+
original: error,
|
|
323
|
+
description: `Extended bottom border at line ${Int.toString(bottomRow + 1)} by ${Int.toString(diff)} characters`,
|
|
324
|
+
line: bottomRow + 1,
|
|
325
|
+
column: closingPlusCol + 1,
|
|
326
|
+
},
|
|
327
|
+
))
|
|
328
|
+
} else {
|
|
329
|
+
None
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
// Top is shorter, need to extend it
|
|
333
|
+
// This is trickier as it affects content alignment
|
|
334
|
+
// For now, we extend the top border
|
|
335
|
+
switch getLine(lines, topLeft.row) {
|
|
336
|
+
| None => None
|
|
337
|
+
| Some(topLine) => {
|
|
338
|
+
let closingPlusCol = topLeft.col + topWidth - 1
|
|
339
|
+
if closingPlusCol >= 0 && closingPlusCol < String.length(topLine) {
|
|
340
|
+
let before = topLine->String.slice(~start=0, ~end=closingPlusCol)
|
|
341
|
+
let after = topLine->String.sliceToEnd(~start=closingPlusCol)
|
|
342
|
+
let dashes = String.repeat("-", Js.Math.abs_int(diff))
|
|
343
|
+
let newLine = before ++ dashes ++ after
|
|
344
|
+
|
|
345
|
+
let newLines = replaceLine(lines, topLeft.row, newLine)
|
|
346
|
+
let fixedText = joinLines(newLines)
|
|
347
|
+
|
|
348
|
+
Some((
|
|
349
|
+
fixedText,
|
|
350
|
+
{
|
|
351
|
+
original: error,
|
|
352
|
+
description: `Extended top border at line ${Int.toString(topLeft.row + 1)} by ${Int.toString(Js.Math.abs_int(diff))} characters`,
|
|
353
|
+
line: topLeft.row + 1,
|
|
354
|
+
column: closingPlusCol + 1,
|
|
355
|
+
},
|
|
356
|
+
))
|
|
357
|
+
} else {
|
|
358
|
+
None
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
| _ => None
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ============================================================================
|
|
374
|
+
// Main Fix Function
|
|
375
|
+
// ============================================================================
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* List of all fix strategies in order of application
|
|
379
|
+
*/
|
|
380
|
+
let fixStrategies: array<(string, ErrorTypes.errorCode => bool, (string, ErrorTypes.t) => option<(string, fixedIssue)>)> = [
|
|
381
|
+
("MisalignedPipe", code => {
|
|
382
|
+
switch code {
|
|
383
|
+
| MisalignedPipe(_) => true
|
|
384
|
+
| _ => false
|
|
385
|
+
}
|
|
386
|
+
}, fixMisalignedPipe),
|
|
387
|
+
("MisalignedClosingBorder", code => {
|
|
388
|
+
switch code {
|
|
389
|
+
| MisalignedClosingBorder(_) => true
|
|
390
|
+
| _ => false
|
|
391
|
+
}
|
|
392
|
+
}, fixMisalignedClosingBorder),
|
|
393
|
+
("UnusualSpacing", code => {
|
|
394
|
+
switch code {
|
|
395
|
+
| UnusualSpacing(_) => true
|
|
396
|
+
| _ => false
|
|
397
|
+
}
|
|
398
|
+
}, fixUnusualSpacing),
|
|
399
|
+
("UnclosedBracket", code => {
|
|
400
|
+
switch code {
|
|
401
|
+
| UnclosedBracket(_) => true
|
|
402
|
+
| _ => false
|
|
403
|
+
}
|
|
404
|
+
}, fixUnclosedBracket),
|
|
405
|
+
("MismatchedWidth", code => {
|
|
406
|
+
switch code {
|
|
407
|
+
| MismatchedWidth(_) => true
|
|
408
|
+
| _ => false
|
|
409
|
+
}
|
|
410
|
+
}, fixMismatchedWidth),
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Try to fix a single error using available strategies
|
|
415
|
+
*/
|
|
416
|
+
let tryFixError = (text: string, error: ErrorTypes.t): option<(string, fixedIssue)> => {
|
|
417
|
+
// Find the first strategy that can fix this error
|
|
418
|
+
fixStrategies->Array.reduce(None, (acc, (_, canFix, apply)) => {
|
|
419
|
+
switch acc {
|
|
420
|
+
| Some(_) => acc // Already found a fix
|
|
421
|
+
| None => {
|
|
422
|
+
if canFix(error.code) {
|
|
423
|
+
apply(text, error)
|
|
424
|
+
} else {
|
|
425
|
+
None
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Check if an error code is fixable
|
|
434
|
+
*/
|
|
435
|
+
let isFixable = (code: ErrorTypes.errorCode): bool => {
|
|
436
|
+
fixStrategies->Array.some(((_, canFix, _)) => canFix(code))
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Main fix function - attempts to fix all errors in the text
|
|
441
|
+
*
|
|
442
|
+
* This function:
|
|
443
|
+
* 1. Parses the text to get errors/warnings
|
|
444
|
+
* 2. Attempts to fix each fixable error
|
|
445
|
+
* 3. Re-parses after each fix to get updated positions
|
|
446
|
+
* 4. Returns the fixed text and list of applied fixes
|
|
447
|
+
*
|
|
448
|
+
* @param text The wireframe markdown text
|
|
449
|
+
* @returns FixResult with fixed text and details
|
|
450
|
+
*/
|
|
451
|
+
@genType
|
|
452
|
+
let fix = (text: string): fixResult => {
|
|
453
|
+
// Maximum iterations to prevent infinite loops
|
|
454
|
+
let maxIterations = 100
|
|
455
|
+
|
|
456
|
+
// Recursive fix loop
|
|
457
|
+
let rec fixLoop = (currentText: string, fixedSoFar: array<fixedIssue>, iteration: int): fixResult => {
|
|
458
|
+
if iteration >= maxIterations {
|
|
459
|
+
// Too many iterations, return what we have
|
|
460
|
+
Ok({
|
|
461
|
+
text: currentText,
|
|
462
|
+
fixed: fixedSoFar,
|
|
463
|
+
remaining: [],
|
|
464
|
+
})
|
|
465
|
+
} else {
|
|
466
|
+
// Parse current text to get errors
|
|
467
|
+
let parseResult = Parser.parse(currentText)
|
|
468
|
+
|
|
469
|
+
switch parseResult {
|
|
470
|
+
| Ok((_ast, warnings)) => {
|
|
471
|
+
// Parse succeeded, only warnings remain
|
|
472
|
+
// Try to fix warnings
|
|
473
|
+
let fixableWarnings = warnings->Array.filter(w => isFixable(w.code))
|
|
474
|
+
|
|
475
|
+
if Array.length(fixableWarnings) === 0 {
|
|
476
|
+
// No more fixable issues
|
|
477
|
+
Ok({
|
|
478
|
+
text: currentText,
|
|
479
|
+
fixed: fixedSoFar,
|
|
480
|
+
remaining: warnings->Array.filter(w => !isFixable(w.code)),
|
|
481
|
+
})
|
|
482
|
+
} else {
|
|
483
|
+
// Try to fix the first fixable warning
|
|
484
|
+
switch fixableWarnings->Array.get(0) {
|
|
485
|
+
| None => Ok({
|
|
486
|
+
text: currentText,
|
|
487
|
+
fixed: fixedSoFar,
|
|
488
|
+
remaining: warnings,
|
|
489
|
+
})
|
|
490
|
+
| Some(warning) => {
|
|
491
|
+
switch tryFixError(currentText, warning) {
|
|
492
|
+
| None => {
|
|
493
|
+
// Couldn't fix, move on
|
|
494
|
+
Ok({
|
|
495
|
+
text: currentText,
|
|
496
|
+
fixed: fixedSoFar,
|
|
497
|
+
remaining: warnings,
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
| Some((newText, fixedIssue)) => {
|
|
501
|
+
// Fixed! Continue with remaining
|
|
502
|
+
let newFixed = fixedSoFar->Array.concat([fixedIssue])
|
|
503
|
+
fixLoop(newText, newFixed, iteration + 1)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
| Error(errors) => {
|
|
511
|
+
// Parse failed, try to fix errors
|
|
512
|
+
let fixableErrors = errors->Array.filter(e => isFixable(e.code))
|
|
513
|
+
|
|
514
|
+
if Array.length(fixableErrors) === 0 {
|
|
515
|
+
// No fixable errors, return what we have
|
|
516
|
+
Ok({
|
|
517
|
+
text: currentText,
|
|
518
|
+
fixed: fixedSoFar,
|
|
519
|
+
remaining: errors->Array.filter(e => !isFixable(e.code)),
|
|
520
|
+
})
|
|
521
|
+
} else {
|
|
522
|
+
// Try to fix the first fixable error
|
|
523
|
+
switch fixableErrors->Array.get(0) {
|
|
524
|
+
| None => Ok({
|
|
525
|
+
text: currentText,
|
|
526
|
+
fixed: fixedSoFar,
|
|
527
|
+
remaining: errors,
|
|
528
|
+
})
|
|
529
|
+
| Some(error) => {
|
|
530
|
+
switch tryFixError(currentText, error) {
|
|
531
|
+
| None => {
|
|
532
|
+
// Couldn't fix, return remaining errors
|
|
533
|
+
Ok({
|
|
534
|
+
text: currentText,
|
|
535
|
+
fixed: fixedSoFar,
|
|
536
|
+
remaining: errors,
|
|
537
|
+
})
|
|
538
|
+
}
|
|
539
|
+
| Some((newText, fixedIssue)) => {
|
|
540
|
+
// Fixed! Continue fixing
|
|
541
|
+
let newFixed = fixedSoFar->Array.concat([fixedIssue])
|
|
542
|
+
fixLoop(newText, newFixed, iteration + 1)
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Start the fix loop
|
|
554
|
+
fixLoop(text, [], 0)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Convenience function - fix and return just the fixed text
|
|
559
|
+
* Returns the original text if nothing was fixed
|
|
560
|
+
*/
|
|
561
|
+
@genType
|
|
562
|
+
let fixOnly = (text: string): string => {
|
|
563
|
+
switch fix(text) {
|
|
564
|
+
| Ok({text: fixedText, _}) => fixedText
|
|
565
|
+
| Error(_) => text
|
|
566
|
+
}
|
|
567
|
+
}
|
|
@@ -11,12 +11,19 @@ import type {t as ErrorTypes_t} from '../../src/parser/Errors/ErrorTypes.gen';
|
|
|
11
11
|
|
|
12
12
|
export abstract class DomBindings_element { protected opaque!: any }; /* simulate opaque types */
|
|
13
13
|
|
|
14
|
+
/** * Scene change callback type.
|
|
15
|
+
* Called when navigating between scenes.
|
|
16
|
+
* @param fromScene The scene ID navigating from (None if initial)
|
|
17
|
+
* @param toScene The scene ID navigating to */
|
|
18
|
+
export type onSceneChangeCallback = (_1:(undefined | string), _2:string) => void;
|
|
19
|
+
|
|
14
20
|
/** * Configuration for the rendering process. */
|
|
15
21
|
export type renderOptions = {
|
|
16
22
|
readonly theme: (undefined | string);
|
|
17
23
|
readonly interactive: boolean;
|
|
18
24
|
readonly injectStyles: boolean;
|
|
19
|
-
readonly containerClass: (undefined | string)
|
|
25
|
+
readonly containerClass: (undefined | string);
|
|
26
|
+
readonly onSceneChange: (undefined | onSceneChangeCallback)
|
|
20
27
|
};
|
|
21
28
|
|
|
22
29
|
/** * Scene management interface returned by render function. */
|
|
@@ -12,7 +12,8 @@ let defaultOptions = {
|
|
|
12
12
|
theme: undefined,
|
|
13
13
|
interactive: true,
|
|
14
14
|
injectStyles: true,
|
|
15
|
-
containerClass: undefined
|
|
15
|
+
containerClass: undefined,
|
|
16
|
+
onSceneChange: undefined
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
let defaultStyles = `
|
|
@@ -280,7 +281,7 @@ function renderScene(scene, onAction) {
|
|
|
280
281
|
return sceneEl;
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
function createSceneManager(scenes) {
|
|
284
|
+
function createSceneManager(scenes, onSceneChange) {
|
|
284
285
|
let currentScene = {
|
|
285
286
|
contents: undefined
|
|
286
287
|
};
|
|
@@ -290,10 +291,11 @@ function createSceneManager(scenes) {
|
|
|
290
291
|
let forwardStack = {
|
|
291
292
|
contents: []
|
|
292
293
|
};
|
|
293
|
-
let switchToScene = id => {
|
|
294
|
-
let
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
let switchToScene = (id, notifyOpt) => {
|
|
295
|
+
let notify = notifyOpt !== undefined ? notifyOpt : true;
|
|
296
|
+
let previousScene = currentScene.contents;
|
|
297
|
+
if (previousScene !== undefined) {
|
|
298
|
+
let el = scenes.get(previousScene);
|
|
297
299
|
if (el !== undefined) {
|
|
298
300
|
Primitive_option.valFromOption(el).classList.remove("active");
|
|
299
301
|
}
|
|
@@ -302,7 +304,15 @@ function createSceneManager(scenes) {
|
|
|
302
304
|
if (el$1 !== undefined) {
|
|
303
305
|
Primitive_option.valFromOption(el$1).classList.add("active");
|
|
304
306
|
currentScene.contents = id;
|
|
305
|
-
|
|
307
|
+
if (notify && (previousScene === undefined || previousScene !== id)) {
|
|
308
|
+
if (onSceneChange !== undefined) {
|
|
309
|
+
return onSceneChange(previousScene, id);
|
|
310
|
+
} else {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
306
316
|
}
|
|
307
317
|
};
|
|
308
318
|
let goto = id => {
|
|
@@ -311,7 +321,7 @@ function createSceneManager(scenes) {
|
|
|
311
321
|
historyStack.contents = historyStack.contents.concat([currentId]);
|
|
312
322
|
forwardStack.contents = [];
|
|
313
323
|
}
|
|
314
|
-
switchToScene(id);
|
|
324
|
+
switchToScene(id, undefined);
|
|
315
325
|
};
|
|
316
326
|
let back = () => {
|
|
317
327
|
let history = historyStack.contents;
|
|
@@ -328,7 +338,7 @@ function createSceneManager(scenes) {
|
|
|
328
338
|
forwardStack.contents = forwardStack.contents.concat([currentId]);
|
|
329
339
|
}
|
|
330
340
|
historyStack.contents = history.slice(0, len - 1 | 0);
|
|
331
|
-
switchToScene(prevId);
|
|
341
|
+
switchToScene(prevId, undefined);
|
|
332
342
|
};
|
|
333
343
|
let forward = () => {
|
|
334
344
|
let fwdStack = forwardStack.contents;
|
|
@@ -345,7 +355,7 @@ function createSceneManager(scenes) {
|
|
|
345
355
|
historyStack.contents = historyStack.contents.concat([currentId]);
|
|
346
356
|
}
|
|
347
357
|
forwardStack.contents = fwdStack.slice(0, len - 1 | 0);
|
|
348
|
-
switchToScene(nextId);
|
|
358
|
+
switchToScene(nextId, undefined);
|
|
349
359
|
};
|
|
350
360
|
let refresh = () => {
|
|
351
361
|
let id = currentScene.contents;
|
|
@@ -451,7 +461,7 @@ function render(ast, options) {
|
|
|
451
461
|
app.appendChild(sceneEl);
|
|
452
462
|
sceneMap.set(scene.id, sceneEl);
|
|
453
463
|
});
|
|
454
|
-
let manager = createSceneManager(sceneMap);
|
|
464
|
+
let manager = createSceneManager(sceneMap, opts.onSceneChange);
|
|
455
465
|
gotoRef.contents = manager.goto;
|
|
456
466
|
backRef.contents = manager.back;
|
|
457
467
|
forwardRef.contents = manager.forward;
|