graphql.ts 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. /**
  2. * Copyright (c) 2021 GraphQL Contributors
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. import CodeMirror from "codemirror"
  10. import {
  11. LexRules,
  12. ParseRules,
  13. isIgnored,
  14. onlineParser,
  15. State,
  16. } from "graphql-language-service-parser"
  17. /**
  18. * The GraphQL mode is defined as a tokenizer along with a list of rules, each
  19. * of which is either a function or an array.
  20. *
  21. * * Function: Provided a token and the stream, returns an expected next step.
  22. * * Array: A list of steps to take in order.
  23. *
  24. * A step is either another rule, or a terminal description of a token. If it
  25. * is a rule, that rule is pushed onto the stack and the parsing continues from
  26. * that point.
  27. *
  28. * If it is a terminal description, the token is checked against it using a
  29. * `match` function. If the match is successful, the token is colored and the
  30. * rule is stepped forward. If the match is unsuccessful, the remainder of the
  31. * rule is skipped and the previous rule is advanced.
  32. *
  33. * This parsing algorithm allows for incremental online parsing within various
  34. * levels of the syntax tree and results in a structured `state` linked-list
  35. * which contains the relevant information to produce valuable typeaheads.
  36. */
  37. CodeMirror.defineMode("graphql", (config) => {
  38. const parser = onlineParser({
  39. eatWhitespace: (stream) => stream.eatWhile(isIgnored),
  40. lexRules: LexRules,
  41. parseRules: ParseRules,
  42. editorConfig: { tabSize: 2 },
  43. })
  44. return {
  45. config,
  46. startState: parser.startState,
  47. token: parser.token as unknown as CodeMirror.Mode<any>["token"], // TODO: Check if the types are indeed compatible
  48. indent,
  49. electricInput: /^\s*[})\]]/,
  50. fold: "brace",
  51. lineComment: "#",
  52. closeBrackets: {
  53. pairs: '()[]{}""',
  54. explode: "()[]{}",
  55. },
  56. }
  57. })
  58. // Seems the electricInput type in @types/codemirror is wrong (i.e it is written as electricinput instead of electricInput)
  59. function indent(
  60. this: CodeMirror.Mode<any> & {
  61. electricInput?: RegExp
  62. config?: CodeMirror.EditorConfiguration
  63. },
  64. state: State,
  65. textAfter: string
  66. ) {
  67. const levels = state.levels
  68. // If there is no stack of levels, use the current level.
  69. // Otherwise, use the top level, pre-emptively dedenting for close braces.
  70. const level =
  71. !levels || levels.length === 0
  72. ? state.indentLevel
  73. : levels[levels.length - 1] -
  74. (this.electricInput?.test(textAfter) ? 1 : 0)
  75. return (level || 0) * (this.config?.indentUnit || 0)
  76. }