diff --git a/components/graphql/RequestOptions.vue b/components/graphql/RequestOptions.vue index 31da91239..af80ffef1 100644 --- a/components/graphql/RequestOptions.vue +++ b/components/graphql/RequestOptions.vue @@ -300,6 +300,7 @@ import { getCurrentStrategyID } from "~/helpers/network" import { makeGQLRequest } from "~/helpers/types/HoppGQLRequest" import { useCodemirror } from "~/helpers/editor/codemirror" import "codemirror/mode/javascript/javascript" +import "~/helpers/editor/modes/graphql" import jsonLinter from "~/helpers/editor/linting/json" import { createGQLQueryLinter } from "~/helpers/editor/linting/gqlQuery" import queryCompleter from "~/helpers/editor/completion/gqlQuery" @@ -365,7 +366,7 @@ const schemaString = useReadonlyStream(props.conn.schema$, null) useCodemirror(queryEditor, gqlQueryString, { extendedEditorConfig: { - mode: "application/ld+json", + mode: "graphql", }, linter: createGQLQueryLinter(schemaString), completer: queryCompleter(schemaString), diff --git a/helpers/editor/modes/graphql.ts b/helpers/editor/modes/graphql.ts new file mode 100644 index 000000000..2c4949882 --- /dev/null +++ b/helpers/editor/modes/graphql.ts @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2021 GraphQL Contributors + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +import CodeMirror from "codemirror" +import { + LexRules, + ParseRules, + isIgnored, + onlineParser, + State, +} from "graphql-language-service-parser" + +/** + * The GraphQL mode is defined as a tokenizer along with a list of rules, each + * of which is either a function or an array. + * + * * Function: Provided a token and the stream, returns an expected next step. + * * Array: A list of steps to take in order. + * + * A step is either another rule, or a terminal description of a token. If it + * is a rule, that rule is pushed onto the stack and the parsing continues from + * that point. + * + * If it is a terminal description, the token is checked against it using a + * `match` function. If the match is successful, the token is colored and the + * rule is stepped forward. If the match is unsuccessful, the remainder of the + * rule is skipped and the previous rule is advanced. + * + * This parsing algorithm allows for incremental online parsing within various + * levels of the syntax tree and results in a structured `state` linked-list + * which contains the relevant information to produce valuable typeaheads. + */ +CodeMirror.defineMode("graphql", (config) => { + const parser = onlineParser({ + eatWhitespace: (stream) => stream.eatWhile(isIgnored), + lexRules: LexRules, + parseRules: ParseRules, + editorConfig: { tabSize: 2 }, + }) + + return { + config, + startState: parser.startState, + token: parser.token as unknown as CodeMirror.Mode["token"], // TODO: Check if the types are indeed compatible + indent, + electricInput: /^\s*[})\]]/, + fold: "brace", + lineComment: "#", + closeBrackets: { + pairs: '()[]{}""', + explode: "()[]{}", + }, + } +}) + +// Seems the electricInput type in @types/codemirror is wrong (i.e it is written as electricinput instead of electricInput) +function indent( + this: CodeMirror.Mode & { + electricInput?: RegExp + config?: CodeMirror.EditorConfiguration + }, + state: State, + textAfter: string +) { + const levels = state.levels + // If there is no stack of levels, use the current level. + // Otherwise, use the top level, pre-emptively dedenting for close braces. + const level = + !levels || levels.length === 0 + ? state.indentLevel + : levels[levels.length - 1] - + (this.electricInput?.test(textAfter) ? 1 : 0) + return (level || 0) * (this.config?.indentUnit || 0) +} diff --git a/package-lock.json b/package-lock.json index 4b7db0678..298ea0f2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "hoppscotch", "version": "2.0.0", "dependencies": { "@apollo/client": "^3.4.10", @@ -25,6 +26,7 @@ "fuse.js": "^6.4.6", "graphql": "^15.5.0", "graphql-language-service-interface": "^2.8.4", + "graphql-language-service-parser": "^1.9.2", "json-loader": "^0.5.7", "lodash": "^4.17.21", "mustache": "^4.2.0", diff --git a/package.json b/package.json index ed6999125..fee85f548 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "fuse.js": "^6.4.6", "graphql": "^15.5.0", "graphql-language-service-interface": "^2.8.4", + "graphql-language-service-parser": "^1.9.2", "json-loader": "^0.5.7", "lodash": "^4.17.21", "mustache": "^4.2.0",