skir-codemirror-plugin 0.9.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.
Files changed (72) hide show
  1. package/README.md +126 -0
  2. package/dist/codemirror/create_editor_state.d.ts +20 -0
  3. package/dist/codemirror/create_editor_state.d.ts.map +1 -0
  4. package/dist/codemirror/create_editor_state.js +252 -0
  5. package/dist/codemirror/create_editor_state.js.map +1 -0
  6. package/dist/codemirror/enter_key_handler.d.ts +8 -0
  7. package/dist/codemirror/enter_key_handler.d.ts.map +1 -0
  8. package/dist/codemirror/enter_key_handler.js +181 -0
  9. package/dist/codemirror/enter_key_handler.js.map +1 -0
  10. package/dist/codemirror/json_completion.d.ts +4 -0
  11. package/dist/codemirror/json_completion.d.ts.map +1 -0
  12. package/dist/codemirror/json_completion.js +150 -0
  13. package/dist/codemirror/json_completion.js.map +1 -0
  14. package/dist/codemirror/json_linter.d.ts +4 -0
  15. package/dist/codemirror/json_linter.d.ts.map +1 -0
  16. package/dist/codemirror/json_linter.js +277 -0
  17. package/dist/codemirror/json_linter.js.map +1 -0
  18. package/dist/codemirror/json_state.d.ts +16 -0
  19. package/dist/codemirror/json_state.d.ts.map +1 -0
  20. package/dist/codemirror/json_state.js +124 -0
  21. package/dist/codemirror/json_state.js.map +1 -0
  22. package/dist/codemirror/status_bar.d.ts +3 -0
  23. package/dist/codemirror/status_bar.d.ts.map +1 -0
  24. package/dist/codemirror/status_bar.js +123 -0
  25. package/dist/codemirror/status_bar.js.map +1 -0
  26. package/dist/index.d.ts +4 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/json/json_parser.d.ts +3 -0
  31. package/dist/json/json_parser.d.ts.map +1 -0
  32. package/dist/json/json_parser.js +414 -0
  33. package/dist/json/json_parser.js.map +1 -0
  34. package/dist/json/json_parser.test.d.ts +2 -0
  35. package/dist/json/json_parser.test.d.ts.map +1 -0
  36. package/dist/json/json_parser.test.js +337 -0
  37. package/dist/json/json_parser.test.js.map +1 -0
  38. package/dist/json/schema_validator.d.ts +3 -0
  39. package/dist/json/schema_validator.d.ts.map +1 -0
  40. package/dist/json/schema_validator.js +525 -0
  41. package/dist/json/schema_validator.js.map +1 -0
  42. package/dist/json/schema_validator.test.d.ts +2 -0
  43. package/dist/json/schema_validator.test.d.ts.map +1 -0
  44. package/dist/json/schema_validator.test.js +212 -0
  45. package/dist/json/schema_validator.test.js.map +1 -0
  46. package/dist/json/to_json.d.ts +6 -0
  47. package/dist/json/to_json.d.ts.map +1 -0
  48. package/dist/json/to_json.js +61 -0
  49. package/dist/json/to_json.js.map +1 -0
  50. package/dist/json/to_json.test.d.ts +2 -0
  51. package/dist/json/to_json.test.d.ts.map +1 -0
  52. package/dist/json/to_json.test.js +128 -0
  53. package/dist/json/to_json.test.js.map +1 -0
  54. package/dist/json/types.d.ts +170 -0
  55. package/dist/json/types.d.ts.map +1 -0
  56. package/dist/json/types.js +2 -0
  57. package/dist/json/types.js.map +1 -0
  58. package/package.json +85 -0
  59. package/src/codemirror/create_editor_state.ts +278 -0
  60. package/src/codemirror/enter_key_handler.ts +232 -0
  61. package/src/codemirror/json_completion.ts +182 -0
  62. package/src/codemirror/json_linter.ts +358 -0
  63. package/src/codemirror/json_state.ts +170 -0
  64. package/src/codemirror/status_bar.ts +137 -0
  65. package/src/index.ts +6 -0
  66. package/src/json/json_parser.test.ts +360 -0
  67. package/src/json/json_parser.ts +461 -0
  68. package/src/json/schema_validator.test.ts +230 -0
  69. package/src/json/schema_validator.ts +558 -0
  70. package/src/json/to_json.test.ts +150 -0
  71. package/src/json/to_json.ts +70 -0
  72. package/src/json/types.ts +254 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json_completion.d.ts","sourceRoot":"","sources":["../../src/codemirror/json_completion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAML,cAAc,EACf,MAAM,eAAe,CAAC;AAGvB,wBAAgB,cAAc,CAC5B,MAAM,EAAE,cAAc,GACrB,CAAC,OAAO,EAAE,iBAAiB,KAAK,gBAAgB,GAAG,IAAI,CAsJzD"}
@@ -0,0 +1,150 @@
1
+ import { ensureJsonState, jsonStateField } from "./json_state";
2
+ export function jsonCompletion(schema) {
3
+ // Index record definitions by record id.
4
+ const idToRecordDef = {};
5
+ for (const record of schema.records) {
6
+ idToRecordDef[record.id] = record;
7
+ }
8
+ function doCompleteJson(jsonValue, position) {
9
+ if (!inSegment(position, jsonValue.segment)) {
10
+ return null;
11
+ }
12
+ const { expectedType } = jsonValue;
13
+ if (!expectedType) {
14
+ return null;
15
+ }
16
+ const actualType = expectedType.kind === "optional" ? expectedType.value : expectedType;
17
+ void actualType;
18
+ switch (jsonValue.kind) {
19
+ case "array": {
20
+ if (expectedType.kind !== "array") {
21
+ return null;
22
+ }
23
+ for (const element of jsonValue.values) {
24
+ if (position < element.firstToken.start) {
25
+ return null; // No need to continue (optimization)
26
+ }
27
+ const maybeResult = doCompleteJson(element, position);
28
+ if (maybeResult) {
29
+ return maybeResult;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ case "object": {
35
+ if (expectedType.kind !== "record") {
36
+ return null;
37
+ }
38
+ const recordDef = idToRecordDef[expectedType.value];
39
+ if (recordDef.kind === "struct") {
40
+ for (const key of jsonValue.allKeys) {
41
+ // First, see if the current position is within the key.
42
+ const { keySegment } = key;
43
+ if (inSegment(position, keySegment)) {
44
+ const missingFieldNames = collectMissingFieldNames(jsonValue, recordDef);
45
+ return {
46
+ from: keySegment.start + 1,
47
+ to: keySegment.end - 1,
48
+ options: missingFieldNames.map((name) => ({
49
+ label: name,
50
+ })),
51
+ };
52
+ }
53
+ // Then, check the value.
54
+ const keyValue = jsonValue.keyValues[key.key];
55
+ if (keyValue) {
56
+ const maybeResult = doCompleteJson(keyValue.value, position);
57
+ if (maybeResult) {
58
+ return maybeResult;
59
+ }
60
+ }
61
+ }
62
+ }
63
+ else {
64
+ const kindKv = jsonValue.keyValues["kind"];
65
+ if (kindKv &&
66
+ inSegment(position, kindKv.value.firstToken) &&
67
+ kindKv.value.kind === "literal" &&
68
+ kindKv.value.type === "string") {
69
+ const options = recordDef.variants
70
+ .filter((v) => v.type)
71
+ .map((v) => ({
72
+ label: v.name,
73
+ }));
74
+ return {
75
+ from: kindKv.value.firstToken.start + 1,
76
+ to: kindKv.value.firstToken.end - 1,
77
+ options: options,
78
+ };
79
+ }
80
+ const valueKv = jsonValue.keyValues["value"];
81
+ if (valueKv) {
82
+ const maybeResult = doCompleteJson(valueKv?.value, position);
83
+ if (maybeResult) {
84
+ return maybeResult;
85
+ }
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+ case "literal": {
91
+ if (expectedType.kind !== "record") {
92
+ return null;
93
+ }
94
+ const recordDef = idToRecordDef[expectedType.value];
95
+ if (recordDef.kind !== "enum") {
96
+ return null;
97
+ }
98
+ const options = recordDef.variants
99
+ .filter((v) => !v.type)
100
+ .map((v) => ({
101
+ label: v.name,
102
+ }))
103
+ .concat({
104
+ label: "UNKNOWN",
105
+ });
106
+ return {
107
+ from: jsonValue.firstToken.start + 1,
108
+ to: jsonValue.firstToken.end - 1,
109
+ options: options,
110
+ };
111
+ }
112
+ }
113
+ }
114
+ function completeJson(context) {
115
+ const position = context.pos;
116
+ // Ensure JSON state is up-to-date
117
+ if (!context.view) {
118
+ return null;
119
+ }
120
+ // Check if character before cursor is a quote
121
+ const charBeforeCursor = position > 0 ? context.state.doc.sliceString(position - 1, position) : "";
122
+ const shouldEnsureState = context.explicit || charBeforeCursor === '"';
123
+ const jsonState = shouldEnsureState
124
+ ? ensureJsonState(context.view, schema)
125
+ : context.state.field(jsonStateField, false);
126
+ if (!jsonState) {
127
+ return null;
128
+ }
129
+ const parseResult = jsonState.parseResult;
130
+ if (!parseResult.value) {
131
+ return null;
132
+ }
133
+ return doCompleteJson(parseResult.value, position);
134
+ }
135
+ return completeJson;
136
+ }
137
+ function inSegment(position, segment) {
138
+ return position >= segment.start && position < segment.end;
139
+ }
140
+ function collectMissingFieldNames(object, recordDef) {
141
+ const result = [];
142
+ for (const field of recordDef.fields) {
143
+ if (object.keyValues[field.name]) {
144
+ continue;
145
+ }
146
+ result.push(field.name);
147
+ }
148
+ return result;
149
+ }
150
+ //# sourceMappingURL=json_completion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json_completion.js","sourceRoot":"","sources":["../../src/codemirror/json_completion.ts"],"names":[],"mappings":"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","sourcesContent":["import { CompletionContext, CompletionResult } from \"@codemirror/autocomplete\";\nimport {\n JsonObject,\n JsonValue,\n RecordDefinition,\n Segment,\n StructDefinition,\n TypeDefinition,\n} from \"../json/types\";\nimport { ensureJsonState, jsonStateField } from \"./json_state\";\n\nexport function jsonCompletion(\n schema: TypeDefinition,\n): (context: CompletionContext) => CompletionResult | null {\n // Index record definitions by record id.\n const idToRecordDef: { [id: string]: RecordDefinition } = {};\n for (const record of schema.records) {\n idToRecordDef[record.id] = record;\n }\n\n function doCompleteJson(\n jsonValue: JsonValue,\n position: number,\n ): CompletionResult | null {\n if (!inSegment(position, jsonValue.segment)) {\n return null;\n }\n const { expectedType } = jsonValue;\n if (!expectedType) {\n return null;\n }\n const actualType =\n expectedType.kind === \"optional\" ? expectedType.value : expectedType;\n void actualType;\n switch (jsonValue.kind) {\n case \"array\": {\n if (expectedType.kind !== \"array\") {\n return null;\n }\n for (const element of jsonValue.values) {\n if (position < element.firstToken.start) {\n return null; // No need to continue (optimization)\n }\n const maybeResult = doCompleteJson(element, position);\n if (maybeResult) {\n return maybeResult;\n }\n }\n return null;\n }\n case \"object\": {\n if (expectedType.kind !== \"record\") {\n return null;\n }\n const recordDef = idToRecordDef[expectedType.value];\n if (recordDef.kind === \"struct\") {\n for (const key of jsonValue.allKeys) {\n // First, see if the current position is within the key.\n const { keySegment } = key;\n if (inSegment(position, keySegment)) {\n const missingFieldNames = collectMissingFieldNames(\n jsonValue,\n recordDef,\n );\n return {\n from: keySegment.start + 1,\n to: keySegment.end - 1,\n options: missingFieldNames.map((name) => ({\n label: name,\n })),\n };\n }\n // Then, check the value.\n const keyValue = jsonValue.keyValues[key.key];\n if (keyValue) {\n const maybeResult = doCompleteJson(keyValue.value, position);\n if (maybeResult) {\n return maybeResult;\n }\n }\n }\n } else {\n const kindKv = jsonValue.keyValues[\"kind\"];\n if (\n kindKv &&\n inSegment(position, kindKv.value.firstToken) &&\n kindKv.value.kind === \"literal\" &&\n kindKv.value.type === \"string\"\n ) {\n const options = recordDef.variants\n .filter((v) => v.type)\n .map((v) => ({\n label: v.name,\n }));\n return {\n from: kindKv.value.firstToken.start + 1,\n to: kindKv.value.firstToken.end - 1,\n options: options,\n };\n }\n const valueKv = jsonValue.keyValues[\"value\"];\n if (valueKv) {\n const maybeResult = doCompleteJson(valueKv?.value, position);\n if (maybeResult) {\n return maybeResult;\n }\n }\n }\n return null;\n }\n case \"literal\": {\n if (expectedType.kind !== \"record\") {\n return null;\n }\n const recordDef = idToRecordDef[expectedType.value];\n if (recordDef.kind !== \"enum\") {\n return null;\n }\n const options = recordDef.variants\n .filter((v) => !v.type)\n .map((v) => ({\n label: v.name,\n }))\n .concat({\n label: \"UNKNOWN\",\n });\n return {\n from: jsonValue.firstToken.start + 1,\n to: jsonValue.firstToken.end - 1,\n options: options,\n };\n }\n }\n }\n\n function completeJson(context: CompletionContext): CompletionResult | null {\n const position = context.pos;\n\n // Ensure JSON state is up-to-date\n if (!context.view) {\n return null;\n }\n\n // Check if character before cursor is a quote\n const charBeforeCursor =\n position > 0 ? context.state.doc.sliceString(position - 1, position) : \"\";\n const shouldEnsureState = context.explicit || charBeforeCursor === '\"';\n\n const jsonState = shouldEnsureState\n ? ensureJsonState(context.view, schema)\n : context.state.field(jsonStateField, false);\n\n if (!jsonState) {\n return null;\n }\n const parseResult = jsonState.parseResult;\n if (!parseResult.value) {\n return null;\n }\n return doCompleteJson(parseResult.value, position);\n }\n\n return completeJson;\n}\n\nfunction inSegment(position: number, segment: Segment): boolean {\n return position >= segment.start && position < segment.end;\n}\n\nfunction collectMissingFieldNames(\n object: JsonObject,\n recordDef: StructDefinition,\n): string[] {\n const result: string[] = [];\n for (const field of recordDef.fields) {\n if (object.keyValues[field.name]) {\n continue;\n }\n result.push(field.name);\n }\n return result;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import { Diagnostic } from "@codemirror/lint";
2
+ import { EditorView } from "@codemirror/view";
3
+ export declare function jsonLinter(editable: "editable" | "read-only"): (view: EditorView) => Diagnostic[];
4
+ //# sourceMappingURL=json_linter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json_linter.d.ts","sourceRoot":"","sources":["../../src/codemirror/json_linter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAI9C,wBAAgB,UAAU,CACxB,QAAQ,EAAE,UAAU,GAAG,WAAW,GACjC,CAAC,IAAI,EAAE,UAAU,KAAK,UAAU,EAAE,CAuBpC"}
@@ -0,0 +1,277 @@
1
+ import { ensureJsonState, jsonStateField } from "./json_state";
2
+ export function jsonLinter(editable) {
3
+ function lintJson(view) {
4
+ const jsonState = view.state.field(jsonStateField, false);
5
+ if (!jsonState) {
6
+ return [];
7
+ }
8
+ const parseResult = jsonState.parseResult;
9
+ if (parseResult.errors.length) {
10
+ return parseResult.errors.map(errorToDiagnostic);
11
+ }
12
+ if (!jsonState.validationResult) {
13
+ return [];
14
+ }
15
+ const { errors, hints } = jsonState.validationResult;
16
+ return errors
17
+ .map(errorToDiagnostic)
18
+ .concat(hints.map((hint) => hintToDiagnostic(hint, editable)));
19
+ }
20
+ return lintJson;
21
+ }
22
+ function errorToDiagnostic(error) {
23
+ return {
24
+ from: error.segment.start,
25
+ to: error.segment.end,
26
+ message: error.message,
27
+ severity: "error",
28
+ renderMessage: () => {
29
+ const wrapper = document.createElement("div");
30
+ wrapper.className = "cm-diagnostic-wrapper";
31
+ wrapper.textContent = error.message;
32
+ return wrapper;
33
+ },
34
+ };
35
+ }
36
+ function getStringControlRows(view, typeHint, value, editable) {
37
+ const headerRow = document.createElement("div");
38
+ headerRow.textContent = typeHint.message;
39
+ const jsonCode = value.jsonCode;
40
+ const parsedValue = JSON.parse(jsonCode);
41
+ const controlsRow = document.createElement("div");
42
+ controlsRow.className = "cm-diagnostic-controls";
43
+ const label = document.createElement("span");
44
+ // label.className = "cm-diagnostic-label";
45
+ label.textContent = "Value:";
46
+ const textarea = document.createElement("textarea");
47
+ textarea.className = "cm-diagnostic-textarea";
48
+ textarea.value = parsedValue;
49
+ textarea.rows = 1;
50
+ const isReadOnly = editable === "read-only";
51
+ textarea.readOnly = isReadOnly;
52
+ textarea.addEventListener("keydown", (e) => {
53
+ if (e.key === "Enter" && !e.shiftKey && !isReadOnly) {
54
+ e.preventDefault();
55
+ const newValue = textarea.value;
56
+ const newJsonString = JSON.stringify(newValue);
57
+ view.dispatch({
58
+ changes: {
59
+ from: typeHint.segment.start,
60
+ to: typeHint.segment.end,
61
+ insert: newJsonString,
62
+ },
63
+ });
64
+ }
65
+ });
66
+ controlsRow.appendChild(label);
67
+ controlsRow.appendChild(textarea);
68
+ return [headerRow, controlsRow];
69
+ }
70
+ function getTimestampControlRows(view, typeHint) {
71
+ const headerRow = document.createElement("div");
72
+ headerRow.textContent = typeHint.message;
73
+ let unixMillis;
74
+ {
75
+ const value = typeHint.valueContext.value;
76
+ if (value.kind === "object") {
77
+ const unixMillisKv = value.keyValues["unix_millis"];
78
+ if (unixMillisKv &&
79
+ unixMillisKv.value.kind === "literal" &&
80
+ unixMillisKv.value.type === "number") {
81
+ unixMillis = JSON.parse(unixMillisKv.value.jsonCode);
82
+ if (unixMillis < -8640000000000000 || 8640000000000000 < unixMillis) {
83
+ unixMillis = undefined;
84
+ }
85
+ }
86
+ }
87
+ }
88
+ // Row 1: unix_millis input
89
+ const row1 = document.createElement("div");
90
+ row1.className = "cm-diagnostic-controls";
91
+ const label1 = document.createElement("span");
92
+ label1.className = "cm-diagnostic-label";
93
+ label1.textContent = "unix_millis:";
94
+ const field1 = document.createElement("div");
95
+ field1.className = "cm-timestamp-field";
96
+ const unixMillisInput = document.createElement("input");
97
+ unixMillisInput.type = "text";
98
+ unixMillisInput.className = "cm-diagnostic-input";
99
+ unixMillisInput.value = unixMillis !== undefined ? String(unixMillis) : "";
100
+ const error1 = document.createElement("div");
101
+ error1.className = "cm-diagnostic-error-message";
102
+ error1.style.display = "none";
103
+ field1.appendChild(unixMillisInput);
104
+ field1.appendChild(error1);
105
+ row1.appendChild(label1);
106
+ row1.appendChild(field1);
107
+ // Row 2: ISO 8601 date string input
108
+ const row2 = document.createElement("div");
109
+ row2.className = "cm-diagnostic-controls";
110
+ const label2 = document.createElement("span");
111
+ label2.className = "cm-diagnostic-label";
112
+ label2.textContent = "ISO 8601:";
113
+ const field2 = document.createElement("div");
114
+ field2.className = "cm-timestamp-field";
115
+ const isoInput = document.createElement("input");
116
+ isoInput.type = "text";
117
+ isoInput.className = "cm-diagnostic-input";
118
+ if (unixMillis !== undefined) {
119
+ isoInput.value = new Date(unixMillis).toISOString();
120
+ }
121
+ const error2 = document.createElement("div");
122
+ error2.className = "cm-diagnostic-error-message";
123
+ error2.style.display = "none";
124
+ field2.appendChild(isoInput);
125
+ field2.appendChild(error2);
126
+ row2.appendChild(label2);
127
+ row2.appendChild(field2);
128
+ // Row 3: Now button
129
+ const row3 = document.createElement("div");
130
+ row3.className = "cm-diagnostic-controls";
131
+ const nowButton = document.createElement("button");
132
+ nowButton.className = "cm-diagnostic-button";
133
+ nowButton.textContent = "Now";
134
+ row3.appendChild(nowButton);
135
+ // Helper function to update the editor
136
+ const updateEditor = (millis, formatted) => {
137
+ const value = typeHint.valueContext.value;
138
+ // Check if both keys exist in the object
139
+ if (value.kind === "object") {
140
+ const unixMillisKv = value.keyValues["unix_millis"];
141
+ const formattedKv = value.keyValues["formatted"];
142
+ if (unixMillisKv && formattedKv) {
143
+ // Update both values individually
144
+ view.dispatch({
145
+ changes: [
146
+ {
147
+ from: unixMillisKv.value.segment.start,
148
+ to: unixMillisKv.value.segment.end,
149
+ insert: String(millis),
150
+ },
151
+ {
152
+ from: formattedKv.value.segment.start,
153
+ to: formattedKv.value.segment.end,
154
+ insert: JSON.stringify(formatted),
155
+ },
156
+ ],
157
+ });
158
+ return;
159
+ }
160
+ }
161
+ // Default behavior: replace the whole object
162
+ const newJsonString = `{"unix_millis": ${millis}, "formatted": "${formatted}"}`;
163
+ const segment = typeHint.valueContext.value.segment;
164
+ view.dispatch({
165
+ changes: {
166
+ from: segment.start,
167
+ to: segment.end,
168
+ insert: newJsonString,
169
+ },
170
+ });
171
+ ensureJsonState(view, view.schema);
172
+ };
173
+ // Enter key handler for unix_millis input
174
+ unixMillisInput.addEventListener("keydown", (e) => {
175
+ if (e.key === "Enter") {
176
+ e.preventDefault();
177
+ const value = unixMillisInput.value.trim();
178
+ const millis = Number(value);
179
+ // Validate
180
+ if (value === "" || isNaN(millis) || !Number.isInteger(millis)) {
181
+ error1.textContent = "Must be an integer";
182
+ error1.style.display = "block";
183
+ return;
184
+ }
185
+ if (millis < -8640000000000000 || millis > 8640000000000000) {
186
+ error1.textContent = "Timestamp out of range";
187
+ error1.style.display = "block";
188
+ return;
189
+ }
190
+ error1.style.display = "none";
191
+ error2.style.display = "none";
192
+ // Update ISO field and editor
193
+ const formatted = new Date(millis).toISOString();
194
+ isoInput.value = formatted;
195
+ updateEditor(millis, formatted);
196
+ }
197
+ });
198
+ // Enter key handler for ISO 8601 input
199
+ isoInput.addEventListener("keydown", (e) => {
200
+ if (e.key === "Enter") {
201
+ e.preventDefault();
202
+ const value = isoInput.value.trim();
203
+ const millis = Date.parse(value);
204
+ // Validate
205
+ if (isNaN(millis)) {
206
+ error2.textContent = "Invalid ISO 8601 date string";
207
+ error2.style.display = "block";
208
+ return;
209
+ }
210
+ error1.style.display = "none";
211
+ error2.style.display = "none";
212
+ // Update unix_millis field and editor
213
+ const formatted = new Date(millis).toISOString();
214
+ unixMillisInput.value = String(millis);
215
+ updateEditor(millis, formatted);
216
+ }
217
+ });
218
+ // Now button handler
219
+ nowButton.addEventListener("click", () => {
220
+ const now = Date.now();
221
+ const formatted = new Date(now).toISOString();
222
+ unixMillisInput.value = String(now);
223
+ isoInput.value = formatted;
224
+ error1.style.display = "none";
225
+ error2.style.display = "none";
226
+ updateEditor(now, formatted);
227
+ });
228
+ const controlsRow = document.createElement("div");
229
+ controlsRow.appendChild(row1);
230
+ controlsRow.appendChild(row2);
231
+ controlsRow.appendChild(row3);
232
+ return [headerRow, controlsRow];
233
+ }
234
+ function hintToDiagnostic(typeHint, editable) {
235
+ const { message } = typeHint;
236
+ return {
237
+ from: typeHint.segment.start,
238
+ to: typeHint.segment.end,
239
+ message: "",
240
+ severity: "info",
241
+ renderMessage: (view) => {
242
+ const wrapper = document.createElement("div");
243
+ wrapper.className = "cm-diagnostic-wrapper";
244
+ let rows;
245
+ if ((message === "string" || message === "string?") &&
246
+ typeHint.valueContext &&
247
+ typeHint.valueContext.value.kind === "literal" &&
248
+ typeHint.valueContext.value.type === "string") {
249
+ // Render a string editing control for string hints.
250
+ rows = getStringControlRows(view, typeHint, typeHint.valueContext.value, editable);
251
+ }
252
+ else if ((message === "timestamp" || message === "timestamp?") &&
253
+ typeHint.valueContext &&
254
+ typeHint.valueContext.value.kind === "object" &&
255
+ editable === "editable") {
256
+ // Render a timestamp editing control for timestamp hints.
257
+ rows = getTimestampControlRows(view, typeHint);
258
+ }
259
+ else {
260
+ // Display the message for non-string types
261
+ const pieces = typeof message === "string" ? [message] : message;
262
+ rows = pieces.map((piece) => {
263
+ const row = document.createElement("div");
264
+ row.textContent = piece;
265
+ return row;
266
+ });
267
+ }
268
+ for (const row of rows) {
269
+ row.classList.add("diagnostic-row");
270
+ wrapper.appendChild(row);
271
+ }
272
+ return wrapper;
273
+ },
274
+ markClass: "",
275
+ };
276
+ }
277
+ //# sourceMappingURL=json_linter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json_linter.js","sourceRoot":"","sources":["../../src/codemirror/json_linter.ts"],"names":[],"mappings":"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","sourcesContent":["import { Diagnostic } from \"@codemirror/lint\";\nimport { EditorView } from \"@codemirror/view\";\nimport { Hint, JsonError, JsonLiteral, TypeHint } from \"../json/types\";\nimport { ensureJsonState, jsonStateField } from \"./json_state\";\n\nexport function jsonLinter(\n editable: \"editable\" | \"read-only\",\n): (view: EditorView) => Diagnostic[] {\n function lintJson(view: EditorView): Diagnostic[] {\n const jsonState = view.state.field(jsonStateField, false);\n if (!jsonState) {\n return [];\n }\n\n const parseResult = jsonState.parseResult;\n if (parseResult.errors.length) {\n return parseResult.errors.map(errorToDiagnostic);\n }\n\n if (!jsonState.validationResult) {\n return [];\n }\n\n const { errors, hints } = jsonState.validationResult;\n return errors\n .map(errorToDiagnostic)\n .concat(hints.map((hint) => hintToDiagnostic(hint, editable)));\n }\n\n return lintJson;\n}\n\nfunction errorToDiagnostic(error: JsonError): Diagnostic {\n return {\n from: error.segment.start,\n to: error.segment.end,\n message: error.message,\n severity: \"error\",\n renderMessage: (): Node => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = \"cm-diagnostic-wrapper\";\n wrapper.textContent = error.message;\n return wrapper;\n },\n };\n}\n\nfunction getStringControlRows(\n view: EditorView,\n typeHint: TypeHint,\n value: JsonLiteral,\n editable: \"editable\" | \"read-only\",\n): HTMLDivElement[] {\n const headerRow = document.createElement(\"div\");\n headerRow.textContent = typeHint.message as string;\n\n const jsonCode = value.jsonCode;\n const parsedValue = JSON.parse(jsonCode) as string;\n\n const controlsRow = document.createElement(\"div\");\n controlsRow.className = \"cm-diagnostic-controls\";\n\n const label = document.createElement(\"span\");\n // label.className = \"cm-diagnostic-label\";\n label.textContent = \"Value:\";\n\n const textarea = document.createElement(\"textarea\");\n textarea.className = \"cm-diagnostic-textarea\";\n textarea.value = parsedValue;\n textarea.rows = 1;\n const isReadOnly = editable === \"read-only\";\n textarea.readOnly = isReadOnly;\n\n textarea.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && !e.shiftKey && !isReadOnly) {\n e.preventDefault();\n const newValue = textarea.value;\n const newJsonString = JSON.stringify(newValue);\n\n view.dispatch({\n changes: {\n from: typeHint.segment.start,\n to: typeHint.segment.end,\n insert: newJsonString,\n },\n });\n }\n });\n\n controlsRow.appendChild(label);\n controlsRow.appendChild(textarea);\n\n return [headerRow, controlsRow];\n}\n\nfunction getTimestampControlRows(\n view: EditorView,\n typeHint: TypeHint,\n): HTMLDivElement[] {\n const headerRow = document.createElement(\"div\");\n headerRow.textContent = typeHint.message as string;\n\n let unixMillis: number | undefined;\n {\n const value = typeHint.valueContext!.value;\n if (value.kind === \"object\") {\n const unixMillisKv = value.keyValues[\"unix_millis\"];\n if (\n unixMillisKv &&\n unixMillisKv.value.kind === \"literal\" &&\n unixMillisKv.value.type === \"number\"\n ) {\n unixMillis = JSON.parse(unixMillisKv.value.jsonCode) as number;\n if (unixMillis < -8640000000000000 || 8640000000000000 < unixMillis) {\n unixMillis = undefined;\n }\n }\n }\n }\n\n // Row 1: unix_millis input\n const row1 = document.createElement(\"div\");\n row1.className = \"cm-diagnostic-controls\";\n\n const label1 = document.createElement(\"span\");\n label1.className = \"cm-diagnostic-label\";\n label1.textContent = \"unix_millis:\";\n\n const field1 = document.createElement(\"div\");\n field1.className = \"cm-timestamp-field\";\n\n const unixMillisInput = document.createElement(\"input\");\n unixMillisInput.type = \"text\";\n unixMillisInput.className = \"cm-diagnostic-input\";\n unixMillisInput.value = unixMillis !== undefined ? String(unixMillis) : \"\";\n\n const error1 = document.createElement(\"div\");\n error1.className = \"cm-diagnostic-error-message\";\n error1.style.display = \"none\";\n\n field1.appendChild(unixMillisInput);\n field1.appendChild(error1);\n row1.appendChild(label1);\n row1.appendChild(field1);\n\n // Row 2: ISO 8601 date string input\n const row2 = document.createElement(\"div\");\n row2.className = \"cm-diagnostic-controls\";\n\n const label2 = document.createElement(\"span\");\n label2.className = \"cm-diagnostic-label\";\n label2.textContent = \"ISO 8601:\";\n\n const field2 = document.createElement(\"div\");\n field2.className = \"cm-timestamp-field\";\n\n const isoInput = document.createElement(\"input\");\n isoInput.type = \"text\";\n isoInput.className = \"cm-diagnostic-input\";\n if (unixMillis !== undefined) {\n isoInput.value = new Date(unixMillis).toISOString();\n }\n\n const error2 = document.createElement(\"div\");\n error2.className = \"cm-diagnostic-error-message\";\n error2.style.display = \"none\";\n\n field2.appendChild(isoInput);\n field2.appendChild(error2);\n row2.appendChild(label2);\n row2.appendChild(field2);\n\n // Row 3: Now button\n const row3 = document.createElement(\"div\");\n row3.className = \"cm-diagnostic-controls\";\n\n const nowButton = document.createElement(\"button\");\n nowButton.className = \"cm-diagnostic-button\";\n nowButton.textContent = \"Now\";\n\n row3.appendChild(nowButton);\n\n // Helper function to update the editor\n const updateEditor = (millis: number, formatted: string): void => {\n const value = typeHint.valueContext!.value;\n\n // Check if both keys exist in the object\n if (value.kind === \"object\") {\n const unixMillisKv = value.keyValues[\"unix_millis\"];\n const formattedKv = value.keyValues[\"formatted\"];\n\n if (unixMillisKv && formattedKv) {\n // Update both values individually\n view.dispatch({\n changes: [\n {\n from: unixMillisKv.value.segment.start,\n to: unixMillisKv.value.segment.end,\n insert: String(millis),\n },\n {\n from: formattedKv.value.segment.start,\n to: formattedKv.value.segment.end,\n insert: JSON.stringify(formatted),\n },\n ],\n });\n\n return;\n }\n }\n\n // Default behavior: replace the whole object\n const newJsonString = `{\"unix_millis\": ${millis}, \"formatted\": \"${formatted}\"}`;\n\n const segment = typeHint.valueContext!.value.segment;\n view.dispatch({\n changes: {\n from: segment.start,\n to: segment.end,\n insert: newJsonString,\n },\n });\n\n ensureJsonState(view, (view as any).schema);\n };\n\n // Enter key handler for unix_millis input\n unixMillisInput.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n const value = unixMillisInput.value.trim();\n const millis = Number(value);\n\n // Validate\n if (value === \"\" || isNaN(millis) || !Number.isInteger(millis)) {\n error1.textContent = \"Must be an integer\";\n error1.style.display = \"block\";\n return;\n }\n\n if (millis < -8640000000000000 || millis > 8640000000000000) {\n error1.textContent = \"Timestamp out of range\";\n error1.style.display = \"block\";\n return;\n }\n\n error1.style.display = \"none\";\n error2.style.display = \"none\";\n\n // Update ISO field and editor\n const formatted = new Date(millis).toISOString();\n isoInput.value = formatted;\n updateEditor(millis, formatted);\n }\n });\n\n // Enter key handler for ISO 8601 input\n isoInput.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n const value = isoInput.value.trim();\n const millis = Date.parse(value);\n\n // Validate\n if (isNaN(millis)) {\n error2.textContent = \"Invalid ISO 8601 date string\";\n error2.style.display = \"block\";\n return;\n }\n\n error1.style.display = \"none\";\n error2.style.display = \"none\";\n\n // Update unix_millis field and editor\n const formatted = new Date(millis).toISOString();\n unixMillisInput.value = String(millis);\n updateEditor(millis, formatted);\n }\n });\n\n // Now button handler\n nowButton.addEventListener(\"click\", () => {\n const now = Date.now();\n const formatted = new Date(now).toISOString();\n\n unixMillisInput.value = String(now);\n isoInput.value = formatted;\n error1.style.display = \"none\";\n error2.style.display = \"none\";\n\n updateEditor(now, formatted);\n });\n\n const controlsRow = document.createElement(\"div\");\n controlsRow.appendChild(row1);\n controlsRow.appendChild(row2);\n controlsRow.appendChild(row3);\n\n return [headerRow, controlsRow];\n}\n\nfunction hintToDiagnostic(\n typeHint: Hint,\n editable: \"editable\" | \"read-only\",\n): Diagnostic {\n const { message } = typeHint;\n return {\n from: typeHint.segment.start,\n to: typeHint.segment.end,\n message: \"\",\n severity: \"info\",\n renderMessage: (view): Node => {\n const wrapper = document.createElement(\"div\");\n wrapper.className = \"cm-diagnostic-wrapper\";\n\n let rows: HTMLDivElement[];\n if (\n (message === \"string\" || message === \"string?\") &&\n typeHint.valueContext &&\n typeHint.valueContext.value.kind === \"literal\" &&\n typeHint.valueContext.value.type === \"string\"\n ) {\n // Render a string editing control for string hints.\n rows = getStringControlRows(\n view,\n typeHint,\n typeHint.valueContext.value,\n editable,\n );\n } else if (\n (message === \"timestamp\" || message === \"timestamp?\") &&\n typeHint.valueContext &&\n typeHint.valueContext.value.kind === \"object\" &&\n editable === \"editable\"\n ) {\n // Render a timestamp editing control for timestamp hints.\n rows = getTimestampControlRows(view, typeHint);\n } else {\n // Display the message for non-string types\n const pieces = typeof message === \"string\" ? [message] : message;\n rows = pieces.map((piece) => {\n const row = document.createElement(\"div\");\n row.textContent = piece;\n return row;\n });\n }\n\n for (const row of rows) {\n row.classList.add(\"diagnostic-row\");\n wrapper.appendChild(row);\n }\n\n return wrapper;\n },\n markClass: \"\",\n };\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import { Extension, StateField } from "@codemirror/state";
2
+ import { EditorView } from "@codemirror/view";
3
+ import type { JsonParseResult, TypeDefinition, ValidationResult } from "../json/types";
4
+ export interface JsonState {
5
+ readonly parseResult: JsonParseResult;
6
+ readonly validationResult?: ValidationResult;
7
+ readonly source: string;
8
+ }
9
+ export declare const jsonStateField: StateField<JsonState | null>;
10
+ /**
11
+ * Ensures the JSON state is up-to-date with the current document.
12
+ * If the state is stale or missing, triggers an immediate parse and returns the updated state.
13
+ */
14
+ export declare function ensureJsonState(view: EditorView, schema: TypeDefinition): JsonState;
15
+ export declare function debouncedJsonParser(schema: TypeDefinition): Extension[];
16
+ //# sourceMappingURL=json_state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json_state.d.ts","sourceRoot":"","sources":["../../src/codemirror/json_state.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAET,UAAU,EAEX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAA0B,MAAM,kBAAkB,CAAC;AAGtE,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;IACtC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAID,eAAO,MAAM,cAAc,8BAYzB,CAAC;AAEH;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,cAAc,GACrB,SAAS,CA0BX;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,EAAE,CAiGvE"}
@@ -0,0 +1,124 @@
1
+ import { StateEffect, StateField, Transaction, } from "@codemirror/state";
2
+ import { ViewPlugin } from "@codemirror/view";
3
+ import { parseJsonValue } from "../json/json_parser";
4
+ import { validateSchema } from "../json/schema_validator";
5
+ const updateJsonState = StateEffect.define();
6
+ export const jsonStateField = StateField.define({
7
+ create() {
8
+ return null;
9
+ },
10
+ update(value, tr) {
11
+ for (const effect of tr.effects) {
12
+ if (effect.is(updateJsonState)) {
13
+ return effect.value;
14
+ }
15
+ }
16
+ return value;
17
+ },
18
+ });
19
+ /**
20
+ * Ensures the JSON state is up-to-date with the current document.
21
+ * If the state is stale or missing, triggers an immediate parse and returns the updated state.
22
+ */
23
+ export function ensureJsonState(view, schema) {
24
+ const currentState = view.state.field(jsonStateField, false);
25
+ const source = view.state.doc.toString();
26
+ // If the source hasn't changed, return the current state
27
+ if (currentState && currentState.source === source) {
28
+ return currentState;
29
+ }
30
+ // Parse and validate immediately
31
+ const parseResult = parseJsonValue(source);
32
+ let validationResult;
33
+ if (parseResult.value) {
34
+ validationResult = validateSchema(parseResult.value, schema);
35
+ }
36
+ const newState = { parseResult, validationResult, source };
37
+ // Update the state if it's different
38
+ if (!currentState || currentState !== newState) {
39
+ view.dispatch({
40
+ effects: updateJsonState.of(newState),
41
+ });
42
+ }
43
+ return newState;
44
+ }
45
+ export function debouncedJsonParser(schema) {
46
+ return [
47
+ jsonStateField,
48
+ ViewPlugin.fromClass(class {
49
+ constructor(view) {
50
+ this.timeout = null;
51
+ this.view = view;
52
+ this.scheduleUpdate();
53
+ }
54
+ update(update) {
55
+ if (update.docChanged) {
56
+ const isUndo = update.transactions.some((tr) => tr.annotation(Transaction.userEvent) === "undo" ||
57
+ tr.annotation(Transaction.userEvent) === "redo");
58
+ this.scheduleUpdate(isUndo ? "from-undo" : undefined);
59
+ }
60
+ }
61
+ scheduleUpdate(fromUndo) {
62
+ if (this.timeout !== null) {
63
+ clearTimeout(this.timeout);
64
+ }
65
+ this.timeout = window.setTimeout(() => {
66
+ this.parseJson(fromUndo);
67
+ this.timeout = null;
68
+ }, 200);
69
+ }
70
+ parseJson(fromUndo) {
71
+ const source = this.view.state.doc.toString();
72
+ const parseResult = parseJsonValue(source);
73
+ let validationResult;
74
+ if (parseResult.value) {
75
+ validationResult = validateSchema(parseResult.value, schema);
76
+ }
77
+ const cursorInsideEdit = () => {
78
+ const cursorPos = this.view.state.selection.main.head;
79
+ return parseResult.edits.some((edit) => edit.segment.start <= cursorPos &&
80
+ cursorPos <= edit.segment.end);
81
+ };
82
+ // Apply edits if all these conditions are satisfied:
83
+ // - no error
84
+ // - the cursor is not inside any of the edited segments, to avoid
85
+ // disrupting the user while they're typing
86
+ // - the update is not triggered by an undo operation
87
+ if (!fromUndo &&
88
+ parseResult.edits.length &&
89
+ parseResult.errors.length <= 0 &&
90
+ !cursorInsideEdit()) {
91
+ const changes = parseResult.edits.map((edit) => ({
92
+ from: edit.segment.start,
93
+ to: edit.segment.end,
94
+ insert: edit.replacement,
95
+ }));
96
+ this.view.dispatch({
97
+ changes,
98
+ effects: updateJsonState.of({
99
+ parseResult,
100
+ validationResult,
101
+ source,
102
+ }),
103
+ scrollIntoView: true,
104
+ });
105
+ }
106
+ else {
107
+ this.view.dispatch({
108
+ effects: updateJsonState.of({
109
+ parseResult,
110
+ validationResult,
111
+ source,
112
+ }),
113
+ });
114
+ }
115
+ }
116
+ destroy() {
117
+ if (this.timeout !== null) {
118
+ clearTimeout(this.timeout);
119
+ }
120
+ }
121
+ }),
122
+ ];
123
+ }
124
+ //# sourceMappingURL=json_state.js.map