Variables
Const completion
completion: Extension = graphqlLanguage.data.of({autocomplete(ctx: CompletionContext) {const schema = getSchema(ctx.state);const opts = getOpts(ctx.state);if (!schema) {return null;}const word = ctx.matchBefore(/\w*/);if (!word) {return null;}const lastWordChar = word.text.split('').pop()!;if (!AUTOCOMPLETE_CHARS.test(lastWordChar) && !ctx.explicit) {return null;}const val = ctx.state.doc.toString();const pos = offsetToPos(ctx.state.doc, ctx.pos);const results = getAutocompleteSuggestions(schema,val,pos,undefined,undefined,opts?.autocompleteOptions,);if (results.length === 0) {return null;}return {from: word.from,options: results.map(item => {return {label: item.label,detail: item.detail || '',info(completionData: Completion) {if (opts?.onCompletionInfoRender) {return opts.onCompletionInfoRender(item, ctx, completionData);}if (item.documentation ||(item.isDeprecated && item.deprecationReason)) {const el = document.createElement('div');el.textContent =item.documentation || item.deprecationReason || '';return el;}},};}),};},})
Const graphqlLanguage
graphqlLanguage: LRLanguage = LRLanguage.define({parser: parser.configure({props: [styleTags({Variable: t.variableName,BooleanValue: t.bool,Description: t.string,StringValue: t.string,Comment: t.lineComment,IntValue: t.integer,FloatValue: t.float,EnumValue: t.special(t.name),NullValue: t.null,DirectiveName: t.modifier,[keywords]: t.keyword,OperationType: t.definitionKeyword,FieldName: t.propertyName,Field: t.propertyName,ArgumentAttributeName: t.attributeName,Name: t.atom,'( )': t.paren,'{ }': t.brace,',': t.separator,[punctuations]: t.punctuation,}),// https://codemirror.net/docs/ref/#language.indentNodePropindentNodeProp.add({[nodesWithBraces]: delimitedIndent({ closing: '}', align: true }),}),foldNodeProp.add({[nodesWithBraces]: foldInside,}),],}),languageData: {commentTokens: { line: '#' },indentOnInput: /^\s*(\{|\})$/,},})
Const jump
jump: Extension = EditorView.domEventHandlers({click(evt, view) {const schema = getSchema(view.state);if (!schema) {return;}// TODO: Set class on cm-editor when mod key is pressed, to style cursor and tokensconst currentPosition = view.state.selection.main.head;const pos = offsetToPos(view.state.doc, currentPosition);const token = getTokenAtPosition(view.state.doc.toString(), pos);const tInfo = getTypeInfo(schema, token.state);const opts = getOpts(view.state);if (opts?.onShowInDocs && isMetaKeyPressed(evt)) {opts.onShowInDocs(tInfo.fieldDef?.name,tInfo.type?.toString(),tInfo.parentType?.toString(),);}},})
Const lint
lint: Extension = linter(view => {const schema = getSchema(view.state);const options = getOpts(view.state);if (!schema) {return [];}const validationErrors = validateSchema(schema);if (validationErrors.length) {if (!options?.showErrorOnInvalidSchema) {return [];}const combinedError = validationErrors.map(error => {return error.message;});return [{from: 0,to: view.state.doc.length,severity: 'error',message: combinedError.join('\n'),actions: [], // TODO:},];}const results = getDiagnostics(view.state.doc.toString(), schema);return results.map((item): Diagnostic | null => {if (!item.severity || !item.source) {return null;}const calculatedFrom = posToOffset(view.state.doc,new Position(item.range.start.line, item.range.start.character),);const from = Math.max(0,Math.min(calculatedFrom, view.state.doc.length),);const calculatedRo = posToOffset(view.state.doc,new Position(item.range.end.line, item.range.end.character - 1),);const to = Math.min(Math.max(from + 1, calculatedRo),view.state.doc.length,);return {from,to: from === to ? to + 1 : to,severity: SEVERITY[item.severity - 1],// source: item.source, // TODO:message: item.message,actions: [], // TODO:};}).filter((_): _ is Diagnostic => Boolean(_));},{needsRefresh(vu) {return (vu.startState.field(schemaStateField) !==vu.state.field(schemaStateField) ||vu.startState.field(optionsStateField) !==vu.state.field(optionsStateField));},},)
Const optionsStateField
options
StateField: StateField<void | GqlExtensionsOptions> = StateField.define<GqlExtensionsOptions | void>({create() {},update(opts, tr) {for (const e of tr.effects) {if (e.is(optionsEffect)) {return e.value;}}return opts;},},)
Const parser
parser: LRParser
Const schemaStateField
schemaStateField: StateField<void | GraphQLSchema> = StateField.define<GraphQLSchema | void>({create() {},update(schema, tr) {for (const e of tr.effects) {if (e.is(schemaEffect)) {return e.value;}}return schema;},})
CodeMirror 6 GraphQL Language extension
Discord Channel
Provides CodeMirror 6 extension with a parser mode for GraphQL along with autocomplete and linting powered by your GraphQL Schema.
Getting Started
CodeMirror 6 customization is done through extensions. This package is an extension that customizes CodeMirror 6 for GraphQL.
import { basicSetup, EditorView } from 'codemirror'; import { graphql } from 'cm6-graphql'; const view = new EditorView({ doc: `mutation mutationName { setString(value: "newString") }`, extensions: [basicSetup, graphql(myGraphQLSchema)], parent: document.body, });
Note: You have to provide a theme to CodeMirror 6 for the styling you want. You can take a look at this example or see the CodeMirror 6 documentation examples for more details.
Updating schema
If you need to dynamically update the GraphQL schema used in the editor, you can call
updateSchema
with the CodeMirrorEditorView
instance and the new schemaimport { updateSchema } from 'cm6-graphql'; const onNewSchema = schema => { updateSchema(view, schema); };