Skip to content

Commit 549367b

Browse files
committed
feat: add json-records language
1 parent f2dafbf commit 549367b

File tree

4 files changed

+89
-13
lines changed

4 files changed

+89
-13
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type * as MonacoTypes from 'monaco-editor'
2+
import * as json_parser from 'jsonc-parser'
3+
4+
export function setupJsonRecordsValidation(monaco: typeof MonacoTypes) {
5+
const markerId = 'json-records-validation'
6+
7+
function validate(model: MonacoTypes.editor.ITextModel) {
8+
const markers: MonacoTypes.editor.IMarkerData[] = []
9+
const text = model.getValue();
10+
11+
if (text) {
12+
const separator = text[text.length - 1];
13+
const splits = text.split(separator);
14+
let offset = 0;
15+
for (let i = 0; i < splits.length; i++) {
16+
const part = splits[i];
17+
if (part) {
18+
let errors: json_parser.ParseError[] = []
19+
json_parser.parse(part, errors, { allowTrailingComma: false, disallowComments: true });
20+
console.log(`Validating JSON record part [${i}] `, part, `error ${JSON.stringify(errors)}`);
21+
if (errors) {
22+
errors.forEach((err) => {
23+
markers.push({
24+
severity: monaco.MarkerSeverity.Error,
25+
startLineNumber: i,
26+
startColumn: offset, // +1 for the separator
27+
endLineNumber: i,
28+
endColumn: offset + err.length,
29+
message: err.error.toString(),
30+
})
31+
});
32+
}
33+
}
34+
35+
offset += part.length + 1; // +1 for the separator
36+
}
37+
}
38+
39+
monaco.editor.setModelMarkers(model, markerId, markers)
40+
}
41+
42+
const contentChangeListeners = new Map<MonacoTypes.editor.ITextModel, MonacoTypes.IDisposable>()
43+
function manageContentChangeListener(model: MonacoTypes.editor.ITextModel) {
44+
const isJsonRecords = model.getModeId() === 'json-records'
45+
const listener = contentChangeListeners.get(model)
46+
47+
if (isJsonRecords && !listener) {
48+
contentChangeListeners.set(
49+
model,
50+
model.onDidChangeContent(() => validate(model))
51+
)
52+
validate(model)
53+
} else if (!isJsonRecords && listener) {
54+
listener.dispose()
55+
contentChangeListeners.delete(model)
56+
monaco.editor.setModelMarkers(model, markerId, [])
57+
}
58+
}
59+
60+
monaco.editor.onWillDisposeModel(model => {
61+
contentChangeListeners.delete(model)
62+
})
63+
monaco.editor.onDidChangeModelLanguage(({ model }) => {
64+
manageContentChangeListener(model)
65+
})
66+
monaco.editor.onDidCreateModel(model => {
67+
manageContentChangeListener(model)
68+
})
69+
}

src/components/editor/monaco.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { delay } from '../../util/promise';
77
import { asError } from '../../util/error';
88
import { observable, runInAction } from 'mobx';
99
import { setupXMLValidation } from './xml-validation';
10+
import { setupJsonRecordsValidation } from './json-records-validation';
1011

1112
export type {
1213
MonacoTypes,
@@ -75,7 +76,12 @@ async function loadMonacoEditor(retries = 5): Promise<void> {
7576
},
7677
});
7778

79+
monaco.languages.register({
80+
id: 'json-records'
81+
});
82+
7883
setupXMLValidation(monaco);
84+
setupJsonRecordsValidation(monaco);
7985

8086
MonacoEditor = rmeModule.default;
8187
} catch (err) {

src/model/events/body-formatting.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { WorkerFormatterKey } from '../../services/ui-worker-formatters';
99
import { formatBufferAsync } from '../../services/ui-worker-api';
1010
import { ReadOnlyParams } from '../../components/common/editable-params';
1111
import { ImageViewer } from '../../components/editor/image-viewer';
12+
import { Buffer } from 'buffer';
1213

1314
export interface EditorFormatter {
1415
language: string;
@@ -128,35 +129,32 @@ export const Formatters: { [key in ViewableContentType]: Formatter } = {
128129
}
129130
},
130131
'json-records': {
131-
language: 'json',
132+
language: 'json-records',
132133
cacheKey: Symbol('json-records'),
133134
isEditApplicable: false,
134135
render: (input: Buffer, headers?: Headers) => {
135136
if (input.byteLength < 10_000) {
136137
try {
137-
let records = new Array();
138+
let records = new Array<string>();
138139
const separator = input[input.length - 1];
140+
const separatorString = Buffer.of(separator).toString('utf8');
141+
139142
splitBuffer(input, separator).forEach((recordBuffer: Buffer) => {
140143
if (recordBuffer.length > 0) {
141144
const record = recordBuffer.toString('utf-8');
142-
records.push(JSON.parse(record.trim()));
145+
records.push(record + separatorString);
143146
}
144147
});
145-
// For short-ish inputs, we return synchronously - conveniently this avoids
146-
// showing the loading spinner that churns the layout in short content cases.
147-
return JSON.stringify(
148-
records,
149-
null,
150-
2
151-
);
148+
149+
return records.join('');
152150
// ^ Same logic as in UI-worker-formatter
153151
} catch (e) {
154152
// Fallback to showing the raw un-formatted:
155153
return bufferToString(input);
156154
}
157155
} else {
158156
return observablePromise(
159-
formatBufferAsync(input, 'json', headers)
157+
formatBufferAsync(input, 'json-records', headers)
160158
);
161159
}
162160
}

src/services/ui-worker-formatters.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,16 @@ const WorkerFormatters = {
8484
try {
8585
let records = new Array();
8686
const separator = content[content.length - 1];
87+
const separatorString = Buffer.of(separator).toString('utf8');
88+
8789
splitBuffer(content, separator).forEach((recordBuffer: Buffer) => {
8890
if (recordBuffer.length > 0) {
8991
const record = recordBuffer.toString('utf-8');
90-
records.push(JSON.parse(record.trim()));
92+
records.push(record + separatorString);
9193
}
9294
});
93-
return JSON.stringify(records, null, 2);
95+
96+
return records.join('');
9497
} catch (e) {
9598
return content.toString('utf8');
9699
}

0 commit comments

Comments
 (0)