Skip to content

Commit

Permalink
improve docs, cleanup, add changeset
Browse files Browse the repository at this point in the history
  • Loading branch information
acao committed Nov 7, 2023
1 parent 6f29786 commit 4ac83af
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 42 deletions.
42 changes: 42 additions & 0 deletions .changeset/strange-chicken-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
"codemirror-json-schema": minor
---

**breaking change**: only impacts those following the "custom usage" approach, it _does not_ effect users using the high level, "bundled" `jsonSchema()` or `json5Schema()` modes.

Previously, we ask you to pass schema to each of the linter, completion and hover extensions.

Now, we ask you to use these new exports to instantiate your schema like this, with `stateExtensions(schema)` as a new extension, and the only one that you pass schema to, like so:

```ts
import type { JSONSchema7 } from "json-schema";
import { json, jsonLanguage, jsonParseLinter } from "@codemirror/lang-json";
import { hoverTooltip } from "@codemirror/view";
import { linter } from "@codemirror/lint";

import {
jsonCompletion,
handleRefresh,
jsonSchemaLinter,
jsonSchemaHover,
stateExtensions,
} from "codemirror-json-schema";

import schema from "./myschema.json";

// ...
extensions: [
json(),
linter(jsonParseLinter()),
linter(jsonSchemaLinter(), {
needsRefresh: handleRefresh,
}),
jsonLanguage.data.of({
autocomplete: jsonCompletion(),
}),
hoverTooltip(jsonSchemaHover()),
// this is where we pass the schema!
// very important!!!!
stateExtensions(schema),
];
```
123 changes: 97 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,29 @@ Codemirror 6 extensions that provide full [JSON Schema](https://json-schema.org/

## Features

This is now a full-featured library for both json4 (aka json) and json5, but the APIs may still have breakages.
This is now a full-featured library for both json4 (aka json) and json5 using extensions, so they should compatible with any frontend framework and/or integration library

So far mostly tested with standard json4 schema specs. See
- ✅ validation messages
- ✅ autocompletion with insert text
- ✅ hover tooltips
- ✅ dynamic, per-editor-instance schemas using codemirror `StateField` and linting refresh

### json4
## Resources

- ✅ validates json
- ✅ autocompletion
- ✅ hover tooltips from schema

### json5

- ✅ validates json5
- ✅ autocompletion
- ✅ hover tooltips
- [Changelog](./CHANGELOG.md)
- [Comprehensive example](https://github.com/acao/cm6-json-schema/blob/main/dev/index.ts)
- [API Docs](./docs/)

## Usage

To give you as much flexibility as possible, everything codemirror related is a peer or optional dependency

Based on whether you want to support json4, json5 or both, you will need to install the relevant language mode for our library to use.

### Breaking Changes:

- 0.5.0 - this breaking change _does not_ effect users using `jsonSchema()` or `json5Schema()` modes, but those using the "custom path".

### json4

with `auto-install-peers true` or similar:
Expand Down Expand Up @@ -76,8 +77,15 @@ This approach allows you to configure the json mode and parse linter, as well as
import { EditorState } from "@codemirror/state";
import { linter } from "@codemirror/lint";
import { hoverTooltip } from "@codemirror/view";
import { json, jsonLanguage, jsonParseLinter } from "@codemirror/lang-json";
import { jsonCompletion jsonSchemaLinter, jsonSchemaHover } from "codemirror-json-schema";
import { json, jsonParseLinter, jsonLanguage } from "@codemirror/lang-json";

import {
jsonSchemaLinter,
jsonSchemaHover,
jsonCompletion,
stateExtensions,
handleRefresh
} from "codemirror-json-schema";

const schema = {
type: "object",
Expand All @@ -96,11 +104,14 @@ const state = EditorState.create({
// default is 750ms
delay: 300
}),
linter(jsonSchemaLinter(schema)),
linter(jsonSchemaLinter(), {
needsRefresh: handleRefresh,
}),
jsonLanguage.data.of({
autocomplete: jsonCompletion(schema),
autocomplete: jsonCompletion(),
}),
hoverTooltip(jsonSchemaHover(schema)),
hoverTooltip(jsonSchemaHover()),
stateExtensions(schema)
];
})
```
Expand Down Expand Up @@ -159,6 +170,7 @@ import {
json5SchemaHover,
json5Completion,
} from "codemirror-json-schema/json5";
import { stateExtensions, handleRefresh } from "codemirror-json-schema";

const schema = {
type: "object",
Expand All @@ -180,30 +192,89 @@ const json5State = EditorState.create({
// the default linting delay is 750ms
delay: 300,
}),
linter(json5SchemaLinter(schema)),
hoverTooltip(json5SchemaHover(schema)),
linter(
json5SchemaLinter({
needsRefresh: handleRefresh,
})
),
hoverTooltip(json5SchemaHover()),
json5Language.data.of({
autocomplete: json5Completion(schema),
autocomplete: json5Completion(),
}),
stateExtensions(schema),
],
});
```

### Complete demo
### Dynamic Schema

If you want to, you can provide schema dynamically, in several ways.
This works the same for either json or json5, using the underlying codemirror 6 StateFields, via the `updateSchema` method export.

In this example

- the initial schema state is empty
- schema is loaded dynamically based on user input
- the linting refresh will be handled automatically, because it's built into our bundled `jsonSchema()` and `json5Schema()` modes

```ts
import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";

import { json5Schema } from "codemirror-json-schema/json5";

import { updateSchema } from "codemirror-json-schema";

const json5State = EditorState.create({
doc: `{
example: true,
// json5 is awesome!
}`,
// note: you can still provide initial
// schema when creating state
extensions: [json5Schema()],
});

You can start with the [deployed example](https://github.com/acao/cm6-json-schema/blob/main/dev/index.ts) to see a more comprehensive setup.
const editor = new EditorView({ state: json5State });

const schemaSelect = document.getElementById("schema-selection");

schemaSelect!.onchange = async (e) => {
const val = e.target!.value!;
if (!val) {
return;
}
// parse the remote schema spec to json
const data = await (
await fetch(`https://json.schemastore.org/${val}`)
).json();
// this will update the schema state field, in an editor specific way
updateSchema(editor, data);
};
```

if you are using the "custom path" with this approach, you will need to configure linting refresh as well:

### API Docs
```ts
import { linter } from "@codemirror/lint";
import { json5SchemaLinter } from "codemirror-json-schema/json5";
import { handleRefresh } from "codemirror-json-schema";

For more information, see the [API Docs](./docs/)
const state = EditorState.create({
// ...
extensions: [
linter(json5SchemaLinter(), {
needsRefresh: handleRefresh,
})
];
}
```
## Current Constraints:
- it only works with one json schema instance at a time, and doesn't yet fetch remote schemas. schema service coming soon!
- currently only tested with standard schemas using json4 spec. results may vary
- doesn't place cursor inside known insert text yet
- currently you can only override the texts and rendering of a hover. we plan to add the same for validation errors and autocomplete
- json5 properties on autocompletion selection will insert surrounding double quotes, but we plan to make it insert without delimiters
## Inspiration
Expand Down
6 changes: 3 additions & 3 deletions dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ const editor2 = new EditorView({
state: json5State,
parent: document.querySelector("#editor-json5")!,
});
const handleSchema = (newSchema: JSONSchema7) => {

const handleSchemaChange = (newSchema: JSONSchema7) => {
updateSchema(editor1, newSchema);
updateSchema(editor2, newSchema);
};

handleSchema(schema);
// new EditorState.fi(editor1, editor2);
// Hot Module Replacement
// if (module.hot) {
Expand All @@ -120,5 +120,5 @@ schemaSelect!.onchange = async (e) => {
const data = await (
await fetch(`https://json.schemastore.org/${val}`)
).json();
handleSchema(data);
handleSchemaChange(data);
};
4 changes: 2 additions & 2 deletions src/bundled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { linter } from "@codemirror/lint";
* Full featured cm6 extension for json, including `@codemirror/lang-json`
* @group Bundled Codemirror Extensions
*/
export function jsonSchema(schema: JSONSchema7) {
export function jsonSchema(schema?: JSONSchema7) {
return [
json(),
linter(jsonParseLinter()),
Expand All @@ -23,6 +23,6 @@ export function jsonSchema(schema: JSONSchema7) {
autocomplete: jsonCompletion(),
}),
hoverTooltip(jsonSchemaHover()),
stateExtensions(),
stateExtensions(schema),
];
}
1 change: 0 additions & 1 deletion src/json-completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { jsonPointerForPosition } from "./utils/jsonPointers.js";
import { TOKENS } from "./constants.js";
import { getJSONSchema } from "./state.js";
import getSchema from "./utils/schema-lib/getSchema.js";
import { EditorState } from "@codemirror/state";

function json5PropertyInsertSnippet(rawWord: string, value: string) {
if (rawWord.startsWith('"')) {
Expand Down
1 change: 0 additions & 1 deletion src/json-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export type JSONValidationOptions = {
type JSONValidationSettings = RequiredPick<JSONValidationOptions, "jsonParser">;

export const handleRefresh = (vu: ViewUpdate) => {
console.log("handleRefresh");
return (
vu.startState.field(schemaStateField) !== vu.state.field(schemaStateField)
);
Expand Down
6 changes: 3 additions & 3 deletions src/json5-bundled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ import { handleRefresh } from "./json-validation.js";
* Full featured cm6 extension for json5, including `codemirror-json5`
* @group Bundled Codemirror Extensions
*/
export function json5Schema(schema: JSONSchema7) {
export function json5Schema(schema?: JSONSchema7) {
return [
json5(),
linter(json5ParseLinter()),
linter(json5SchemaLinter(), {
needsRefresh: handleRefresh,
}),
json5Language.data.of({
autocomplete: json5Completion(schema),
autocomplete: json5Completion(),
}),
hoverTooltip(json5SchemaHover(schema)),
hoverTooltip(json5SchemaHover()),
stateExtensions(schema),
];
}
5 changes: 1 addition & 4 deletions src/json5-hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ export type JSON5HoverOptions = Exclude<HoverOptions, "mode">;
* Instantiates a JSONHover instance with the JSON5 mode
* @group Codemirror Extensions
*/
export function json5SchemaHover(
schema: JSONSchema7,
options?: JSON5HoverOptions
) {
export function json5SchemaHover(options?: JSON5HoverOptions) {
const hover = new JSONHover({
...options,
parser: json5.parse,
Expand Down
2 changes: 0 additions & 2 deletions src/json5-validation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { EditorView } from "@codemirror/view";
import { linter } from "@codemirror/lint";
import {
JSONValidation,
handleRefresh,
type JSONValidationOptions,
} from "./json-validation.js";
import { parseJSON5DocumentState } from "./utils/parseJSON5Document.js";
Expand Down

0 comments on commit 4ac83af

Please sign in to comment.