skir-codemirror-plugin 1.0.2 → 1.0.3

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 (42) hide show
  1. package/README.md +15 -6
  2. package/dist/codemirror/create_editor_state.d.ts +4 -2
  3. package/dist/codemirror/create_editor_state.d.ts.map +1 -1
  4. package/dist/codemirror/create_editor_state.js +178 -31
  5. package/dist/codemirror/create_editor_state.js.map +1 -1
  6. package/dist/codemirror/json_linter.d.ts.map +1 -1
  7. package/dist/codemirror/json_linter.js +67 -3
  8. package/dist/codemirror/json_linter.js.map +1 -1
  9. package/dist/codemirror/json_state.d.ts +5 -1
  10. package/dist/codemirror/json_state.d.ts.map +1 -1
  11. package/dist/codemirror/json_state.js +26 -1
  12. package/dist/codemirror/json_state.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/json/json_parser.js +35 -1
  17. package/dist/json/json_parser.js.map +1 -1
  18. package/dist/json/json_parser.test.js +48 -0
  19. package/dist/json/json_parser.test.js.map +1 -1
  20. package/dist/json/schema_validator.d.ts.map +1 -1
  21. package/dist/json/schema_validator.js +16 -11
  22. package/dist/json/schema_validator.js.map +1 -1
  23. package/dist/json/schema_validator.test.js +71 -0
  24. package/dist/json/schema_validator.test.js.map +1 -1
  25. package/dist/json/to_json.d.ts +1 -1
  26. package/dist/json/to_json.d.ts.map +1 -1
  27. package/dist/json/to_json.js +3 -3
  28. package/dist/json/to_json.js.map +1 -1
  29. package/dist/json/types.d.ts +4 -0
  30. package/dist/json/types.d.ts.map +1 -1
  31. package/dist/json/types.js.map +1 -1
  32. package/package.json +2 -3
  33. package/src/codemirror/create_editor_state.ts +272 -31
  34. package/src/codemirror/json_linter.ts +89 -4
  35. package/src/codemirror/json_state.ts +44 -1
  36. package/src/index.ts +1 -0
  37. package/src/json/json_parser.test.ts +51 -0
  38. package/src/json/json_parser.ts +37 -1
  39. package/src/json/schema_validator.test.ts +75 -0
  40. package/src/json/schema_validator.ts +20 -10
  41. package/src/json/to_json.ts +3 -3
  42. package/src/json/types.ts +7 -0
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/json/types.ts"],"names":[],"mappings":"","sourcesContent":["// To use instead of `any`\nexport type Json =\n | null\n | boolean\n | number\n | string\n | readonly Json[]\n | Readonly<{ [name: string]: Json }>;\n\n// -----------------------------------------------------------------------------\n// ERRORS\n// -----------------------------------------------------------------------------\n\nexport interface JsonError {\n readonly kind: \"error\";\n readonly segment: Segment;\n readonly message: string;\n}\n\n// -----------------------------------------------------------------------------\n// PARSING\n// -----------------------------------------------------------------------------\n\nexport interface JsonParseResult {\n /// If `undefined`, then `errors` is guaranteed not to be empty.\n readonly value: JsonValue | undefined;\n readonly errors: readonly JsonError[];\n /// Set of edits to apply to the original JSON code to fix formatting and\n // comma errors (no comma between consecutive values, trailing comma).\n readonly edits: readonly JsonEdit[];\n}\n\nexport type JsonValue = JsonArray | JsonObject | JsonLiteral;\n\nexport interface JsonArray {\n readonly kind: \"array\";\n /// Position of the '[' token.\n readonly firstToken: Segment;\n /// From '[' to ']' included.\n readonly segment: Segment;\n readonly values: JsonValue[];\n expectedType?: TypeSignature;\n}\n\nexport interface JsonKey {\n readonly keySegment: Segment;\n readonly key: string;\n}\n\nexport interface JsonKeyValue {\n readonly keySegment: Segment;\n readonly key: string;\n readonly value: JsonValue;\n expectedType?: TypeSignature;\n}\n\nexport interface JsonObject {\n readonly kind: \"object\";\n /// Position of the '{' token.\n readonly firstToken: Segment;\n /// From '{' to '}' included.\n readonly segment: Segment;\n readonly keyValues: { [key: string]: JsonKeyValue };\n /// Includes \"broken\" keys which produced a parsing error.\n readonly allKeys: readonly JsonKey[];\n expectedType?: TypeSignature;\n}\n\nexport interface JsonLiteral {\n readonly kind: \"literal\";\n /// Position of the first and only token. Same as `segment`.\n readonly firstToken: Segment;\n readonly segment: Segment;\n readonly jsonCode: string;\n readonly type: \"boolean\" | \"null\" | \"number\" | \"string\";\n expectedType?: TypeSignature;\n}\n\nexport interface Segment {\n readonly start: number;\n readonly end: number;\n}\n\n/// Path to a sub-value within a valid Skir value.\nexport type Path =\n | {\n readonly kind: \"root\";\n }\n | {\n readonly kind: \"field-value\";\n /// Name of the field of the struct\n readonly fieldName: string;\n /// Path to the struct value\n readonly structPath: Path;\n }\n | {\n readonly kind: \"variant-value\";\n /// Name of the wrapper variant of the enum\n readonly variantName: string;\n /// Path to the enum value\n readonly enumPath: Path;\n }\n | {\n readonly kind: \"array-item\";\n readonly index: number;\n readonly key: string | null;\n readonly arrayPath: Path;\n };\n\nexport interface JsonValueContext {\n readonly value: JsonValue;\n readonly path: Path;\n}\n\nexport interface JsonEdit {\n readonly segment: Segment;\n readonly replacement: string;\n}\n\n// -----------------------------------------------------------------------------\n// SCHEMA\n// -----------------------------------------------------------------------------\n\n/** JSON representation of a `TypeDescriptor`. */\nexport type TypeDefinition = {\n readonly type: TypeSignature;\n readonly records: readonly RecordDefinition[];\n};\n\n/** A type in the JSON representation of a `TypeDescriptor`. */\nexport type TypeSignature =\n | {\n kind: \"optional\";\n value: TypeSignature;\n }\n | ArrayTypeSignature\n | RecordTypeSignature\n | {\n kind: \"primitive\";\n value: PrimitiveType;\n };\n\nexport interface ArrayTypeSignature {\n readonly kind: \"array\";\n readonly value: {\n readonly item: TypeSignature;\n readonly key_extractor?: string;\n };\n}\n\nexport interface RecordTypeSignature {\n readonly kind: \"record\";\n readonly value: string;\n}\n\nexport type PrimitiveType =\n | \"bool\"\n | \"int32\"\n | \"int64\"\n | \"hash64\"\n | \"float32\"\n | \"float64\"\n | \"timestamp\"\n | \"string\"\n | \"bytes\";\n\nexport type RecordDefinition = StructDefinition | EnumDefinition;\n\n/** Definition of a struct in the JSON representation of a `TypeDescriptor`. */\nexport type StructDefinition = {\n readonly kind: \"struct\";\n readonly id: string;\n readonly doc?: string;\n readonly fields: readonly FieldDefinition[];\n readonly removed_numbers?: readonly number[];\n};\n\n/**\n * Definition of a struct field in the JSON representation of a\n * `TypeDescriptor`.\n */\nexport type FieldDefinition = {\n readonly name: string;\n readonly type: TypeSignature;\n readonly number: number;\n readonly doc?: string;\n};\n\n/** Definition of an enum in the JSON representation of a `TypeDescriptor`. */\nexport type EnumDefinition = {\n readonly kind: \"enum\";\n readonly id: string;\n readonly doc?: string;\n readonly variants: readonly VariantDefinition[];\n readonly removed_numbers?: readonly number[];\n};\n\n/**\n * Definition of an enum variant in the JSON representation of a\n * `TypeDescriptor`.\n */\nexport type VariantDefinition = {\n readonly name: string;\n readonly type?: TypeSignature;\n readonly number: number;\n readonly doc?: string;\n};\n\n// -----------------------------------------------------------------------------\n// SCHEMA VALIDATION\n// -----------------------------------------------------------------------------\n\nexport interface ValidationResult {\n readonly errors: JsonError[];\n readonly hints: Hint[];\n readonly rootTypeHint: TypeHint | undefined;\n readonly pathToTypeHint: ReadonlyMap<Path, TypeHint>;\n}\n\nexport interface TypeHint {\n readonly segment: Segment;\n readonly message: string | readonly string[];\n readonly valueContext: JsonValueContext;\n /// In order. All are included in 'valueContext.value.segment'.\n readonly childHints: readonly TypeHint[];\n}\n\nexport type Hint =\n | {\n readonly segment: Segment;\n readonly message: string;\n readonly valueContext?: undefined;\n }\n | TypeHint;\n\nexport interface MutableTypeHint extends TypeHint {\n readonly childHints: TypeHint[];\n}\n\n// -----------------------------------------------------------------------------\n// METHOD LIST\n// -----------------------------------------------------------------------------\n\nexport interface MethodList {\n readonly methods: readonly Method[];\n}\n\nexport interface Method {\n readonly method: string;\n readonly number: number | string;\n readonly request: TypeDefinition;\n readonly response: TypeDefinition;\n readonly doc?: string;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/json/types.ts"],"names":[],"mappings":"","sourcesContent":["// To use instead of `any`\nexport type Json =\n | null\n | boolean\n | number\n | string\n | readonly Json[]\n | Readonly<{ [name: string]: Json }>;\n\n// -----------------------------------------------------------------------------\n// ERRORS\n// -----------------------------------------------------------------------------\n\nexport interface JsonError {\n readonly kind: \"error\";\n readonly segment: Segment;\n readonly message: string;\n}\n\n// -----------------------------------------------------------------------------\n// PARSING\n// -----------------------------------------------------------------------------\n\nexport interface JsonParseResult {\n /// If `undefined`, then `errors` is guaranteed not to be empty.\n readonly value: JsonValue | undefined;\n readonly errors: readonly JsonError[];\n /// Set of edits to apply to the original JSON code to fix formatting and\n // comma errors (no comma between consecutive values, trailing comma).\n readonly edits: readonly JsonEdit[];\n}\n\nexport type JsonValue = JsonArray | JsonObject | JsonLiteral;\n\nexport interface JsonArray {\n readonly kind: \"array\";\n /// Position of the '[' token.\n readonly firstToken: Segment;\n /// From '[' to ']' included.\n readonly segment: Segment;\n readonly values: JsonValue[];\n readonly indent: number;\n expectedType?: TypeSignature;\n}\n\nexport interface JsonKey {\n readonly keySegment: Segment;\n readonly key: string;\n}\n\nexport interface JsonKeyValue {\n readonly keySegment: Segment;\n readonly key: string;\n readonly value: JsonValue;\n expectedType?: TypeSignature;\n}\n\nexport interface JsonObject {\n readonly kind: \"object\";\n /// Position of the '{' token.\n readonly firstToken: Segment;\n /// From '{' to '}' included.\n readonly segment: Segment;\n readonly keyValues: { [key: string]: JsonKeyValue };\n /// Includes \"broken\" keys which produced a parsing error.\n readonly allKeys: readonly JsonKey[];\n readonly indent: number;\n expectedType?: TypeSignature;\n}\n\nexport interface JsonLiteral {\n readonly kind: \"literal\";\n /// Position of the first and only token. Same as `segment`.\n readonly firstToken: Segment;\n readonly segment: Segment;\n readonly jsonCode: string;\n readonly type: \"boolean\" | \"null\" | \"number\" | \"string\";\n readonly indent: number;\n expectedType?: TypeSignature;\n}\n\nexport interface Segment {\n readonly start: number;\n readonly end: number;\n}\n\n/// Path to a sub-value within a valid Skir value.\nexport type Path =\n | {\n readonly kind: \"root\";\n }\n | {\n readonly kind: \"field-value\";\n /// Name of the field of the struct\n readonly fieldName: string;\n /// Path to the struct value\n readonly structPath: Path;\n }\n | {\n readonly kind: \"variant-value\";\n /// Name of the wrapper variant of the enum\n readonly variantName: string;\n /// Path to the enum value\n readonly enumPath: Path;\n }\n | {\n readonly kind: \"array-item\";\n readonly index: number;\n readonly key: string | null;\n readonly arrayPath: Path;\n };\n\nexport interface JsonValueContext {\n readonly value: JsonValue;\n readonly path: Path;\n}\n\nexport interface JsonEdit {\n readonly segment: Segment;\n readonly replacement: string;\n}\n\n// -----------------------------------------------------------------------------\n// SCHEMA\n// -----------------------------------------------------------------------------\n\n/** JSON representation of a `TypeDescriptor`. */\nexport type TypeDefinition = {\n readonly type: TypeSignature;\n readonly records: readonly RecordDefinition[];\n};\n\n/** A type in the JSON representation of a `TypeDescriptor`. */\nexport type TypeSignature =\n | {\n kind: \"optional\";\n value: TypeSignature;\n }\n | ArrayTypeSignature\n | RecordTypeSignature\n | {\n kind: \"primitive\";\n value: PrimitiveType;\n };\n\nexport interface ArrayTypeSignature {\n readonly kind: \"array\";\n readonly value: {\n readonly item: TypeSignature;\n readonly key_extractor?: string;\n };\n}\n\nexport interface RecordTypeSignature {\n readonly kind: \"record\";\n readonly value: string;\n}\n\nexport type PrimitiveType =\n | \"bool\"\n | \"int32\"\n | \"int64\"\n | \"hash64\"\n | \"float32\"\n | \"float64\"\n | \"timestamp\"\n | \"string\"\n | \"bytes\";\n\nexport type RecordDefinition = StructDefinition | EnumDefinition;\n\n/** Definition of a struct in the JSON representation of a `TypeDescriptor`. */\nexport type StructDefinition = {\n readonly kind: \"struct\";\n readonly id: string;\n readonly doc?: string;\n readonly fields: readonly FieldDefinition[];\n readonly removed_numbers?: readonly number[];\n};\n\n/**\n * Definition of a struct field in the JSON representation of a\n * `TypeDescriptor`.\n */\nexport type FieldDefinition = {\n readonly name: string;\n readonly type: TypeSignature;\n readonly number: number;\n readonly doc?: string;\n};\n\n/** Definition of an enum in the JSON representation of a `TypeDescriptor`. */\nexport type EnumDefinition = {\n readonly kind: \"enum\";\n readonly id: string;\n readonly doc?: string;\n readonly variants: readonly VariantDefinition[];\n readonly removed_numbers?: readonly number[];\n};\n\n/**\n * Definition of an enum variant in the JSON representation of a\n * `TypeDescriptor`.\n */\nexport type VariantDefinition = {\n readonly name: string;\n readonly type?: TypeSignature;\n readonly number: number;\n readonly doc?: string;\n};\n\n// -----------------------------------------------------------------------------\n// SCHEMA VALIDATION\n// -----------------------------------------------------------------------------\n\nexport interface ValidationResult {\n readonly errors: JsonError[];\n readonly hints: Hint[];\n readonly rootTypeHint: TypeHint | undefined;\n readonly pathToTypeHint: ReadonlyMap<Path, TypeHint>;\n}\n\nexport interface TypeHint {\n readonly segment: Segment;\n readonly message: string | readonly string[];\n readonly valueContext: JsonValueContext;\n /// In order. All are included in 'valueContext.value.segment'.\n readonly childHints: readonly TypeHint[];\n /// Set if the expected type is an enum and the value is a literal string.\n /// In that case, the editor can show a dropdown with the possible variants\n /// of the enum.\n readonly enumDefinition?: EnumDefinition;\n}\n\nexport type Hint =\n | {\n readonly segment: Segment;\n readonly message: string;\n readonly valueContext?: undefined;\n }\n | TypeHint;\n\nexport interface MutableTypeHint extends TypeHint {\n readonly childHints: TypeHint[];\n}\n\n// -----------------------------------------------------------------------------\n// METHOD LIST\n// -----------------------------------------------------------------------------\n\nexport interface MethodList {\n readonly methods: readonly Method[];\n}\n\nexport interface Method {\n readonly method: string;\n readonly number: number | string;\n readonly request: TypeDefinition;\n readonly response: TypeDefinition;\n readonly doc?: string;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skir-codemirror-plugin",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "",
5
5
  "homepage": "https://github.com/gepheum/skir-codemirror-plugin#readme",
6
6
  "bugs": {
@@ -46,8 +46,7 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@codemirror/lang-json": "^6.0.2",
49
- "@uiw/codemirror-theme-tokyo-night": "^4.25.4",
50
- "@uiw/codemirror-theme-tokyo-night-day": "^4.25.9",
49
+ "@uiw/codemirror-themes-all": "^4.25.9",
51
50
  "codemirror": "^6.0.2",
52
51
  "skir-client": "^1.0.11"
53
52
  },
@@ -3,8 +3,7 @@ import { json as jsonExtension } from "@codemirror/lang-json";
3
3
  import { linter, lintGutter } from "@codemirror/lint";
4
4
  import { EditorState, Extension } from "@codemirror/state";
5
5
  import { EditorView } from "@codemirror/view";
6
- import { tokyoNight } from "@uiw/codemirror-theme-tokyo-night";
7
- import { tokyoNightDay } from "@uiw/codemirror-theme-tokyo-night-day";
6
+ import * as codeMirrorThemes from "@uiw/codemirror-themes-all";
8
7
  import { basicSetup } from "codemirror";
9
8
  import { makeJsonTemplate } from "../json/to_json";
10
9
  import type { Json, RecordDefinition, TypeDefinition } from "../json/types";
@@ -18,14 +17,279 @@ export type CreateEditorStateParams = {
18
17
  schema: TypeDefinition;
19
18
  readOnly?: true;
20
19
  json?: Json;
21
- theme?: "tokyo-night" | "tokyo-night-day" | CustomTheme;
20
+ theme?: BuiltinThemeName | CustomTheme;
21
+ otherExtension?: Extension;
22
22
  };
23
23
 
24
+ export type BuiltinThemeName =
25
+ | "abcdef"
26
+ | "abyss"
27
+ | "androidstudio"
28
+ | "andromeda"
29
+ | "atomone"
30
+ | "aura"
31
+ | "basic-dark"
32
+ | "basic-light"
33
+ | "bbedit"
34
+ | "bespin"
35
+ | "console-dark"
36
+ | "console-light"
37
+ | "copilot"
38
+ | "darcula"
39
+ | "dracula"
40
+ | "duotone-dark"
41
+ | "duotone-light"
42
+ | "eclipse"
43
+ | "github-dark"
44
+ | "github-light"
45
+ | "gruvbox-dark"
46
+ | "gruvbox-light"
47
+ | "kimbie"
48
+ | "material"
49
+ | "material-dark"
50
+ | "material-light"
51
+ | "monokai"
52
+ | "monokai-dimmed"
53
+ | "noctis-lilac"
54
+ | "nord"
55
+ | "okaidia"
56
+ | "quietlight"
57
+ | "red"
58
+ | "solarized-dark"
59
+ | "solarized-light"
60
+ | "sublime"
61
+ | "tokyo-night"
62
+ | "tokyo-night-day"
63
+ | "tokyo-night-storm"
64
+ | "tomorrow-night-blue"
65
+ | "vscode-dark"
66
+ | "vscode-light"
67
+ | "white"
68
+ | "white-dark"
69
+ | "white-light"
70
+ | "xcode-dark"
71
+ | "xcode-light";
72
+
73
+ const WHITE_THEME_COLORS: Omit<CustomTheme, "themeExtension"> = {
74
+ backgroundColor: "#fffdf7",
75
+ lighterBgColor: "#f3eee1",
76
+ borderColor: "#d4d0c4",
77
+ foregroundColor: "#111111",
78
+ accentColor: "#111111",
79
+ errorColor: "#232323",
80
+ selectionColor: "#d9d4c7",
81
+ };
82
+
83
+ const TOKYO_NIGHT_THEME_COLORS: Omit<CustomTheme, "themeExtension"> = {
84
+ backgroundColor: "#1a1b26",
85
+ lighterBgColor: "#1f2335",
86
+ borderColor: "#414868",
87
+ foregroundColor: "#c0caf5",
88
+ accentColor: "#7aa2f7",
89
+ errorColor: "#f7768e",
90
+ selectionColor: "#515c7e40",
91
+ };
92
+
93
+ const TOKYO_NIGHT_DAY_THEME_COLORS: Omit<CustomTheme, "themeExtension"> = {
94
+ backgroundColor: "#d5d6db",
95
+ lighterBgColor: "#e1e2e7",
96
+ borderColor: "#adb0bb",
97
+ foregroundColor: "#3760bf",
98
+ accentColor: "#2e7de9",
99
+ errorColor: "#f52a65",
100
+ selectionColor: "#3760bf33",
101
+ };
102
+
103
+ type ThemeDefaults = Partial<{
104
+ background: string;
105
+ foreground: string;
106
+ caret: string;
107
+ selection: string;
108
+ selectionMatch: string;
109
+ gutterBackground: string;
110
+ gutterForeground: string;
111
+ gutterBorder: string;
112
+ lineHighlight: string;
113
+ }>;
114
+
115
+ const BUILTIN_THEME_EXTENSIONS: Record<BuiltinThemeName, Extension> = {
116
+ abcdef: codeMirrorThemes.abcdef,
117
+ abyss: codeMirrorThemes.abyss,
118
+ androidstudio: codeMirrorThemes.androidstudio,
119
+ andromeda: codeMirrorThemes.andromeda,
120
+ atomone: codeMirrorThemes.atomone,
121
+ aura: codeMirrorThemes.aura,
122
+ "basic-dark": codeMirrorThemes.basicDark,
123
+ "basic-light": codeMirrorThemes.basicLight,
124
+ bbedit: codeMirrorThemes.bbedit,
125
+ bespin: codeMirrorThemes.bespin,
126
+ "console-dark": codeMirrorThemes.consoleDark,
127
+ "console-light": codeMirrorThemes.consoleLight,
128
+ copilot: codeMirrorThemes.copilot,
129
+ darcula: codeMirrorThemes.darcula,
130
+ dracula: codeMirrorThemes.dracula,
131
+ "duotone-dark": codeMirrorThemes.duotoneDark,
132
+ "duotone-light": codeMirrorThemes.duotoneLight,
133
+ eclipse: codeMirrorThemes.eclipse,
134
+ "github-dark": codeMirrorThemes.githubDark,
135
+ "github-light": codeMirrorThemes.githubLight,
136
+ "gruvbox-dark": codeMirrorThemes.gruvboxDark,
137
+ "gruvbox-light": codeMirrorThemes.gruvboxLight,
138
+ kimbie: codeMirrorThemes.kimbie,
139
+ material: codeMirrorThemes.material,
140
+ "material-dark": codeMirrorThemes.materialDark,
141
+ "material-light": codeMirrorThemes.materialLight,
142
+ monokai: codeMirrorThemes.monokai,
143
+ "monokai-dimmed": codeMirrorThemes.monokaiDimmed,
144
+ "noctis-lilac": codeMirrorThemes.noctisLilac,
145
+ nord: codeMirrorThemes.nord,
146
+ okaidia: codeMirrorThemes.okaidia,
147
+ quietlight: codeMirrorThemes.quietlight,
148
+ red: codeMirrorThemes.red,
149
+ "solarized-dark": codeMirrorThemes.solarizedDark,
150
+ "solarized-light": codeMirrorThemes.solarizedLight,
151
+ sublime: codeMirrorThemes.sublime,
152
+ "tokyo-night": codeMirrorThemes.tokyoNight,
153
+ "tokyo-night-day": codeMirrorThemes.tokyoNightDay,
154
+ "tokyo-night-storm": codeMirrorThemes.tokyoNightStorm,
155
+ "tomorrow-night-blue": codeMirrorThemes.tomorrowNightBlue,
156
+ "vscode-dark": codeMirrorThemes.vscodeDark,
157
+ "vscode-light": codeMirrorThemes.vscodeLight,
158
+ white: codeMirrorThemes.whiteLight,
159
+ "white-dark": codeMirrorThemes.whiteDark,
160
+ "white-light": codeMirrorThemes.whiteLight,
161
+ "xcode-dark": codeMirrorThemes.xcodeDark,
162
+ "xcode-light": codeMirrorThemes.xcodeLight,
163
+ };
164
+
165
+ const BUILTIN_THEME_DEFAULTS: Record<BuiltinThemeName, ThemeDefaults> = {
166
+ abcdef: codeMirrorThemes.defaultSettingsAbcdef,
167
+ abyss: codeMirrorThemes.defaultSettingsAbyss,
168
+ androidstudio: codeMirrorThemes.defaultSettingsAndroidstudio,
169
+ andromeda: codeMirrorThemes.defaultSettingsAndromeda,
170
+ atomone: codeMirrorThemes.defaultSettingsAtomone,
171
+ aura: codeMirrorThemes.defaultSettingsAura,
172
+ "basic-dark": codeMirrorThemes.defaultSettingsBasicDark,
173
+ "basic-light": codeMirrorThemes.defaultSettingsBasicLight,
174
+ bbedit: codeMirrorThemes.defaultSettingsBbedit,
175
+ bespin: codeMirrorThemes.defaultSettingsBespin,
176
+ "console-dark": codeMirrorThemes.defaultSettingsConsoleDark,
177
+ "console-light": codeMirrorThemes.defaultSettingsConsoleLight,
178
+ copilot: codeMirrorThemes.defaultSettingsCopilot,
179
+ darcula: codeMirrorThemes.defaultSettingsDarcula,
180
+ dracula: codeMirrorThemes.defaultSettingsDracula,
181
+ "duotone-dark": codeMirrorThemes.defaultSettingsDuotoneDark,
182
+ "duotone-light": codeMirrorThemes.defaultSettingsDuotoneLight,
183
+ eclipse: codeMirrorThemes.defaultSettingsEclipse,
184
+ "github-dark": codeMirrorThemes.defaultSettingsGithubDark,
185
+ "github-light": codeMirrorThemes.defaultSettingsGithubLight,
186
+ "gruvbox-dark": codeMirrorThemes.defaultSettingsGruvboxDark,
187
+ "gruvbox-light": codeMirrorThemes.defaultSettingsGruvboxLight,
188
+ kimbie: codeMirrorThemes.defaultSettingsKimbie,
189
+ material: codeMirrorThemes.defaultSettingsMaterial,
190
+ "material-dark": codeMirrorThemes.defaultSettingsMaterialDark,
191
+ "material-light": codeMirrorThemes.defaultSettingsMaterialLight,
192
+ monokai: codeMirrorThemes.defaultSettingsMonokai,
193
+ "monokai-dimmed": codeMirrorThemes.defaultSettingsMonokaiDimmed,
194
+ "noctis-lilac": codeMirrorThemes.defaultSettingsNoctisLilac,
195
+ nord: codeMirrorThemes.defaultSettingsNord,
196
+ okaidia: codeMirrorThemes.defaultSettingsOkaidia,
197
+ quietlight: codeMirrorThemes.defaultSettingsQuietlight,
198
+ red: codeMirrorThemes.defaultSettingsRed,
199
+ "solarized-dark": codeMirrorThemes.defaultSettingsSolarizedDark,
200
+ "solarized-light": codeMirrorThemes.defaultSettingsSolarizedLight,
201
+ sublime: codeMirrorThemes.defaultSettingsSublime,
202
+ "tokyo-night": codeMirrorThemes.defaultSettingsTokyoNight,
203
+ "tokyo-night-day": codeMirrorThemes.defaultSettingsTokyoNightDay,
204
+ "tokyo-night-storm": codeMirrorThemes.defaultSettingsTokyoNightStorm,
205
+ "tomorrow-night-blue": codeMirrorThemes.defaultSettingsTomorrowNightBlue,
206
+ "vscode-dark": codeMirrorThemes.defaultSettingsVscodeDark,
207
+ "vscode-light": codeMirrorThemes.defaultSettingsVscodeLight,
208
+ white: codeMirrorThemes.defaultSettingsWhiteLight,
209
+ "white-dark": codeMirrorThemes.defaultSettingsWhiteDark,
210
+ "white-light": codeMirrorThemes.defaultSettingsWhiteLight,
211
+ "xcode-dark": codeMirrorThemes.defaultSettingsXcodeDark,
212
+ "xcode-light": codeMirrorThemes.defaultSettingsXcodeLight,
213
+ };
214
+
215
+ function pickColor(
216
+ ...candidates: Array<string | undefined>
217
+ ): string | undefined {
218
+ for (const candidate of candidates) {
219
+ if (candidate && candidate !== "transparent") {
220
+ return candidate;
221
+ }
222
+ }
223
+ return undefined;
224
+ }
225
+
226
+ function themeFromDefaults(
227
+ defaults: ThemeDefaults,
228
+ themeExtension: Extension,
229
+ ): CustomTheme {
230
+ const background = pickColor(defaults.background, defaults.gutterBackground);
231
+ const foreground = pickColor(
232
+ defaults.caret,
233
+ defaults.foreground,
234
+ defaults.gutterForeground,
235
+ );
236
+ const selection = pickColor(
237
+ defaults.selection,
238
+ defaults.selectionMatch,
239
+ defaults.lineHighlight,
240
+ );
241
+ return {
242
+ backgroundColor: background ?? "#1a1b26",
243
+ lighterBgColor:
244
+ pickColor(
245
+ defaults.gutterBackground,
246
+ defaults.lineHighlight,
247
+ background,
248
+ ) ?? "#1f2335",
249
+ borderColor:
250
+ pickColor(
251
+ defaults.gutterBorder,
252
+ defaults.selectionMatch,
253
+ defaults.selection,
254
+ defaults.foreground,
255
+ ) ?? "#414868",
256
+ foregroundColor: foreground ?? "#c0caf5",
257
+ accentColor: pickColor(defaults.foreground, defaults.caret) ?? "#7aa2f7",
258
+ errorColor: pickColor(defaults.caret, defaults.foreground) ?? "#f7768e",
259
+ selectionColor: selection ?? "#515c7e40",
260
+ themeExtension,
261
+ };
262
+ }
263
+
264
+ function resolveBuiltinTheme(themeName: BuiltinThemeName): CustomTheme {
265
+ const themeExtension = BUILTIN_THEME_EXTENSIONS[themeName];
266
+ if (themeName === "white") {
267
+ return {
268
+ ...WHITE_THEME_COLORS,
269
+ themeExtension,
270
+ };
271
+ }
272
+ if (themeName === "tokyo-night") {
273
+ return {
274
+ ...TOKYO_NIGHT_THEME_COLORS,
275
+ themeExtension,
276
+ };
277
+ }
278
+ if (themeName === "tokyo-night-day") {
279
+ return {
280
+ ...TOKYO_NIGHT_DAY_THEME_COLORS,
281
+ themeExtension,
282
+ };
283
+ }
284
+ return themeFromDefaults(BUILTIN_THEME_DEFAULTS[themeName], themeExtension);
285
+ }
286
+
24
287
  export function createEditorState({
25
288
  schema,
26
289
  readOnly,
27
290
  json,
28
291
  theme,
292
+ otherExtension,
29
293
  }: CreateEditorStateParams): EditorState {
30
294
  const idToRecordDef: { [id: string]: RecordDefinition } = {};
31
295
  for (const record of schema.records) {
@@ -33,34 +297,10 @@ export function createEditorState({
33
297
  }
34
298
  const content = json ?? makeJsonTemplate(schema.type, idToRecordDef);
35
299
 
36
- switch (theme) {
37
- case undefined:
38
- case "tokyo-night": {
39
- theme = {
40
- backgroundColor: "#1a1b26",
41
- lighterBgColor: "#1f2335",
42
- borderColor: "#414868",
43
- foregroundColor: "#c0caf5",
44
- accentColor: "#7aa2f7",
45
- errorColor: "#f7768e",
46
- selectionColor: "#515c7e40",
47
- themeExtension: tokyoNight,
48
- };
49
- break;
50
- }
51
- case "tokyo-night-day": {
52
- theme = {
53
- backgroundColor: "#d5d6db",
54
- lighterBgColor: "#e1e2e7",
55
- borderColor: "#adb0bb",
56
- foregroundColor: "#3760bf",
57
- accentColor: "#2e7de9",
58
- errorColor: "#f52a65",
59
- selectionColor: "#3760bf33",
60
- themeExtension: tokyoNightDay,
61
- };
62
- break;
63
- }
300
+ if (theme === undefined) {
301
+ theme = resolveBuiltinTheme("tokyo-night");
302
+ } else if (typeof theme === "string") {
303
+ theme = resolveBuiltinTheme(theme);
64
304
  }
65
305
 
66
306
  return EditorState.create({
@@ -262,6 +502,7 @@ export function createEditorState({
262
502
  borderTop: "1px solid",
263
503
  },
264
504
  }),
505
+ otherExtension ?? [],
265
506
  ],
266
507
  });
267
508
  }
@@ -1,7 +1,8 @@
1
1
  import { Diagnostic } from "@codemirror/lint";
2
2
  import { EditorView } from "@codemirror/view";
3
+ import { makeJsonTemplate } from "../json/to_json";
3
4
  import { Hint, JsonError, JsonLiteral, TypeHint } from "../json/types";
4
- import { ensureJsonState, jsonStateField } from "./json_state";
5
+ import { jsonStateField } from "./json_state";
5
6
 
6
7
  export function jsonLinter(
7
8
  editable: "editable" | "read-only",
@@ -221,8 +222,6 @@ function getTimestampControlRows(
221
222
  insert: newJsonString,
222
223
  },
223
224
  });
224
-
225
- ensureJsonState(view, (view as any).schema);
226
225
  };
227
226
 
228
227
  // Enter key handler for unix_millis input
@@ -300,6 +299,84 @@ function getTimestampControlRows(
300
299
  return [headerRow, controlsRow];
301
300
  }
302
301
 
302
+ function getEnumDropdown(
303
+ view: EditorView,
304
+ typeHint: TypeHint,
305
+ enumDefinition: NonNullable<TypeHint["enumDefinition"]>,
306
+ ): HTMLDivElement {
307
+ const controlsRow = document.createElement("div");
308
+ controlsRow.className = "cm-diagnostic-controls";
309
+
310
+ const label = document.createElement("span");
311
+ label.className = "cm-diagnostic-label";
312
+ label.textContent = "Variant:";
313
+
314
+ const select = document.createElement("select");
315
+ select.className = "cm-diagnostic-input";
316
+
317
+ {
318
+ // Add the UNKNOWN variant
319
+ const option = document.createElement("option");
320
+ option.value = "UNKNOWN";
321
+ option.textContent = "UNKNOWN";
322
+ select.appendChild(option);
323
+ }
324
+ for (const variant of enumDefinition.variants) {
325
+ const option = document.createElement("option");
326
+ option.value = variant.name;
327
+ option.textContent = variant.name;
328
+ select.appendChild(option);
329
+ }
330
+
331
+ const currentValue = typeHint.valueContext?.value;
332
+ if (
333
+ currentValue &&
334
+ currentValue.kind === "literal" &&
335
+ currentValue.type === "string"
336
+ ) {
337
+ try {
338
+ select.value = JSON.parse(currentValue.jsonCode) as string;
339
+ } catch {
340
+ // Ignore invalid value and keep the default selection.
341
+ }
342
+ }
343
+
344
+ select.addEventListener("change", () => {
345
+ const variant = enumDefinition.variants.find(
346
+ (variant) => variant.name === select.value,
347
+ )!;
348
+ let newJsonString: string;
349
+ if (variant.type) {
350
+ const jsonState = view.state.field(jsonStateField, false)!;
351
+ const valueJson = makeJsonTemplate(
352
+ variant.type,
353
+ jsonState.recordIdToDefinition,
354
+ );
355
+ newJsonString = JSON.stringify(
356
+ {
357
+ kind: variant.name,
358
+ value: valueJson,
359
+ },
360
+ null,
361
+ 2,
362
+ ).replaceAll("\n", "\n" + " ".repeat(typeHint.valueContext.value.indent));
363
+ } else {
364
+ newJsonString = JSON.stringify(variant.name);
365
+ }
366
+ view.dispatch({
367
+ changes: {
368
+ from: typeHint.segment.start,
369
+ to: typeHint.segment.end,
370
+ insert: newJsonString,
371
+ },
372
+ });
373
+ });
374
+
375
+ controlsRow.appendChild(label);
376
+ controlsRow.appendChild(select);
377
+ return controlsRow;
378
+ }
379
+
303
380
  function hintToDiagnostic(
304
381
  typeHint: Hint,
305
382
  editable: "editable" | "read-only",
@@ -337,13 +414,21 @@ function hintToDiagnostic(
337
414
  // Render a timestamp editing control for timestamp hints.
338
415
  rows = getTimestampControlRows(view, typeHint);
339
416
  } else {
340
- // Display the message for non-string types
417
+ // Display the message.
341
418
  const pieces = typeof message === "string" ? [message] : message;
342
419
  rows = pieces.map((piece) => {
343
420
  const row = document.createElement("div");
344
421
  row.textContent = piece;
345
422
  return row;
346
423
  });
424
+ if (
425
+ typeHint.valueContext &&
426
+ typeHint.enumDefinition &&
427
+ editable === "editable"
428
+ ) {
429
+ // Render a dropdown for selecting a different variant.
430
+ rows.push(getEnumDropdown(view, typeHint, typeHint.enumDefinition));
431
+ }
347
432
  }
348
433
 
349
434
  for (const row of rows) {
@@ -9,6 +9,7 @@ import { parseJsonValue } from "../json/json_parser";
9
9
  import { validateSchema } from "../json/schema_validator";
10
10
  import type {
11
11
  JsonParseResult,
12
+ RecordDefinition,
12
13
  TypeDefinition,
13
14
  ValidationResult,
14
15
  } from "../json/types";
@@ -17,6 +18,8 @@ export interface JsonState {
17
18
  readonly parseResult: JsonParseResult;
18
19
  readonly validationResult?: ValidationResult;
19
20
  readonly source: string;
21
+ readonly schema: TypeDefinition;
22
+ readonly recordIdToDefinition: { [id: string]: RecordDefinition };
20
23
  }
21
24
 
22
25
  const updateJsonState = StateEffect.define<JsonState>();
@@ -58,7 +61,15 @@ export function ensureJsonState(
58
61
  validationResult = validateSchema(parseResult.value, schema);
59
62
  }
60
63
 
61
- const newState: JsonState = { parseResult, validationResult, source };
64
+ const recordIdToDefinition = indexRecordDefinitions(schema, currentState);
65
+
66
+ const newState: JsonState = {
67
+ parseResult,
68
+ validationResult,
69
+ source,
70
+ schema,
71
+ recordIdToDefinition,
72
+ };
62
73
 
63
74
  // Update the state if it's different
64
75
  if (!currentState || currentState !== newState) {
@@ -139,21 +150,37 @@ export function debouncedJsonParser(schema: TypeDefinition): Extension[] {
139
150
  insert: edit.replacement,
140
151
  }));
141
152
 
153
+ const oldState = this.view.state.field(jsonStateField, false);
154
+ const recordIdToDefinition = indexRecordDefinitions(
155
+ schema,
156
+ oldState,
157
+ );
158
+
142
159
  this.view.dispatch({
143
160
  changes,
144
161
  effects: updateJsonState.of({
145
162
  parseResult,
146
163
  validationResult,
147
164
  source,
165
+ schema,
166
+ recordIdToDefinition,
148
167
  }),
149
168
  scrollIntoView: true,
150
169
  });
151
170
  } else {
171
+ const oldState = this.view.state.field(jsonStateField, false);
172
+ const recordIdToDefinition = indexRecordDefinitions(
173
+ schema,
174
+ oldState,
175
+ );
176
+
152
177
  this.view.dispatch({
153
178
  effects: updateJsonState.of({
154
179
  parseResult,
155
180
  validationResult,
156
181
  source,
182
+ schema,
183
+ recordIdToDefinition,
157
184
  }),
158
185
  });
159
186
  }
@@ -168,3 +195,19 @@ export function debouncedJsonParser(schema: TypeDefinition): Extension[] {
168
195
  ),
169
196
  ];
170
197
  }
198
+
199
+ function indexRecordDefinitions(
200
+ schema: TypeDefinition,
201
+ oldState: JsonState | null | undefined,
202
+ ): {
203
+ [id: string]: RecordDefinition;
204
+ } {
205
+ if (schema === oldState?.schema) {
206
+ return oldState.recordIdToDefinition;
207
+ }
208
+ const idToRecordDef: { [id: string]: RecordDefinition } = {};
209
+ for (const recordDef of schema.records) {
210
+ idToRecordDef[recordDef.id] = recordDef;
211
+ }
212
+ return idToRecordDef;
213
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { createEditorState } from "./codemirror/create_editor_state";
2
2
  export type {
3
+ BuiltinThemeName,
3
4
  CreateEditorStateParams,
4
5
  CustomTheme,
5
6
  } from "./codemirror/create_editor_state";
@@ -63,6 +63,18 @@ describe("json_parser", () => {
63
63
  });
64
64
  });
65
65
 
66
+ it("tracks indent for top-level literal", () => {
67
+ expect(parseJsonValue(" true")).toMatch({
68
+ value: {
69
+ kind: "literal",
70
+ jsonCode: "true",
71
+ type: "boolean",
72
+ indent: 3,
73
+ },
74
+ errors: [],
75
+ });
76
+ });
77
+
66
78
  it("parses empty array", () => {
67
79
  expect(parseJsonValue("[]")).toMatch({
68
80
  value: { kind: "array", values: [] },
@@ -119,6 +131,45 @@ describe("json_parser", () => {
119
131
  });
120
132
  });
121
133
 
134
+ it("tracks indent from line leading spaces for field values", () => {
135
+ const result = parseJsonValue(`{
136
+ "a": 1,
137
+ "b": [
138
+ 2
139
+ ]
140
+ }`);
141
+
142
+ expect(result).toMatch({
143
+ value: {
144
+ kind: "object",
145
+ indent: 0,
146
+ keyValues: {
147
+ a: {
148
+ value: {
149
+ kind: "literal",
150
+ jsonCode: "1",
151
+ indent: 2,
152
+ },
153
+ },
154
+ b: {
155
+ value: {
156
+ kind: "array",
157
+ indent: 2,
158
+ values: [
159
+ {
160
+ kind: "literal",
161
+ jsonCode: "2",
162
+ indent: 4,
163
+ },
164
+ ],
165
+ },
166
+ },
167
+ },
168
+ },
169
+ errors: [],
170
+ });
171
+ });
172
+
122
173
  it("parses arrays with whitespace", () => {
123
174
  expect(parseJsonValue(" [ 1 ] ")).toMatch({
124
175
  value: {