Browse Source

Merge branch 'feat/codemirror-6'

liyasthomas 4 years ago
parent
commit
76a3b35e9e
27 changed files with 1516 additions and 349 deletions
  1. 4 0
      packages/codemirror-lang-graphql/.gitignore
  2. 5 0
      packages/codemirror-lang-graphql/.npmignore
  3. 1 0
      packages/codemirror-lang-graphql/README.md
  4. 32 0
      packages/codemirror-lang-graphql/package.json
  5. 12 0
      packages/codemirror-lang-graphql/rollup.config.js
  6. 43 0
      packages/codemirror-lang-graphql/src/index.js
  7. 372 0
      packages/codemirror-lang-graphql/src/syntax.grammar
  8. 1 0
      packages/codemirror-lang-graphql/test/cases.txt
  9. 17 0
      packages/codemirror-lang-graphql/test/test.js
  10. 12 0
      packages/codemirror-lang-graphql/tsconfig.json
  11. 6 3
      packages/hoppscotch-app/assets/scss/styles.scss
  12. 46 0
      packages/hoppscotch-app/assets/scss/themes.scss
  13. 0 2
      packages/hoppscotch-app/components/graphql/RequestOptions.vue
  14. 0 1
      packages/hoppscotch-app/components/graphql/Sidebar.vue
  15. 0 1
      packages/hoppscotch-app/components/http/ImportCurl.vue
  16. 0 1
      packages/hoppscotch-app/components/http/Parameters.vue
  17. 0 1
      packages/hoppscotch-app/components/http/PreRequestScript.vue
  18. 0 5
      packages/hoppscotch-app/components/http/RawBody.vue
  19. 0 1
      packages/hoppscotch-app/components/http/Tests.vue
  20. 0 4
      packages/hoppscotch-app/components/lenses/renderers/HTMLLensRenderer.vue
  21. 0 1
      packages/hoppscotch-app/components/lenses/renderers/JSONLensRenderer.vue
  22. 0 1
      packages/hoppscotch-app/components/lenses/renderers/XMLLensRenderer.vue
  23. 278 169
      packages/hoppscotch-app/helpers/editor/codemirror.ts
  24. 0 80
      packages/hoppscotch-app/helpers/editor/modes/graphql.ts
  25. 233 0
      packages/hoppscotch-app/helpers/editor/themes/baseTheme.ts
  26. 19 2
      packages/hoppscotch-app/package.json
  27. 435 77
      pnpm-lock.yaml

+ 4 - 0
packages/codemirror-lang-graphql/.gitignore

@@ -0,0 +1,4 @@
+/node_modules
+package-lock.json
+/dist
+/src/*.d.ts

+ 5 - 0
packages/codemirror-lang-graphql/.npmignore

@@ -0,0 +1,5 @@
+/src
+/test
+/node_modules
+rollup.config.js
+tsconfig.json

+ 1 - 0
packages/codemirror-lang-graphql/README.md

@@ -0,0 +1 @@
+A [CodeMirror 6](https://codemirror.net/6) language plugin for GraphQL

+ 32 - 0
packages/codemirror-lang-graphql/package.json

@@ -0,0 +1,32 @@
+{
+  "name": "@hoppscotch/codemirror-lang-graphql",
+  "version": "0.1.0",
+  "description": "GraphQL language support for CodeMirror",
+  "scripts": {
+    "test": "mocha test/test.js",
+    "prepare": "rollup -c"
+  },
+  "type": "module",
+  "main": "dist/index.cjs",
+  "module": "dist/index.js",
+  "exports": {
+    "import": "./dist/index.js",
+    "require": "./dist/index.cjs"
+  },
+  "types": "dist/index.d.ts",
+  "sideEffects": false,
+  "dependencies": {
+    "@codemirror/highlight": "^0.19.0",
+    "@codemirror/language": "^0.19.0",
+    "@lezer/lr": "^0.15.0"
+  },
+  "devDependencies": {
+    "@lezer/generator": "^0.15.0",
+    "mocha": "^9.0.1",
+    "rollup": "^2.35.1",
+    "rollup-plugin-dts": "^3.0.2",
+    "rollup-plugin-ts": "^1.4.0",
+    "typescript": "^4.3.4"
+  },
+  "license": "MIT"
+}

+ 12 - 0
packages/codemirror-lang-graphql/rollup.config.js

@@ -0,0 +1,12 @@
+import typescript from "rollup-plugin-ts"
+import {lezer} from "@lezer/generator/rollup"
+
+export default {
+  input: "src/index.js",
+  external: id => id != "tslib" && !/^(\.?\/|\w:)/.test(id),
+  output: [
+    {file: "dist/index.cjs", format: "cjs"},
+    {dir: "./dist", format: "es"}
+  ],
+  plugins: [lezer(), typescript()]
+}

+ 43 - 0
packages/codemirror-lang-graphql/src/index.js

@@ -0,0 +1,43 @@
+import {parser} from "./syntax.grammar"
+import {LRLanguage, LanguageSupport, indentNodeProp, foldNodeProp, foldInside,  delimitedIndent} from "@codemirror/language"
+import {styleTags, tags as t} from "@codemirror/highlight"
+
+export const GQLLanguage = LRLanguage.define({
+  parser: parser.configure({
+    props: [
+      indentNodeProp.add({
+        "SelectionSet FieldsDefinition ObjectValue SchemaDefinition RootTypeDef": delimitedIndent({ closing: "}", align: true }),
+      }),
+      foldNodeProp.add({
+        Application: foldInside,
+        "SelectionSet FieldsDefinition ObjectValue RootOperationTypeDefinition RootTypeDef": (node) => {
+          return {
+            from: node.from,
+            to: node.to
+          }
+
+        }
+      }),
+      styleTags({
+        Name: t.definition(t.variableName),
+        "OperationDefinition/Name": t.definition(t.function(t.variableName)),
+        OperationType: t.keyword,
+        BooleanValue: t.bool,
+        StringValue: t.string,
+        IntValue: t.number,
+        FloatValue: t.number,
+        NullValue: t.null,
+        ObjectValue: t.brace,
+        Comment: t.lineComment,
+      })
+    ]
+  }),
+  languageData: {
+    commentTokens: { line: "#" },
+    closeBrackets: { brackets: ["(", "[", "{", '"', '"""'] }
+  }
+})
+
+export function GQL() {
+  return new LanguageSupport(GQLLanguage)
+}

+ 372 - 0
packages/codemirror-lang-graphql/src/syntax.grammar

@@ -0,0 +1,372 @@
+@top SourceFile {
+  Document
+}
+
+@precedence {
+  fieldDef @right,
+  typeDef @right
+}
+
+Document {
+  Definition+
+}
+
+Definition {
+  ExecutableDefinition |
+  TypeSystemDefinition |
+  TypeSystemExtension
+}
+
+ExecutableDefinition {
+  OperationDefinition |
+  FragmentDefinition
+}
+
+TypeSystemDefinition {
+  SchemaDefinition |
+  TypeDefinition |
+  DirectiveDefinition
+}
+
+TypeSystemExtension {
+  SchemaExtension |
+  TypeExtension
+}
+
+SchemaDefinition {
+  Description? @specialize<Name, "schema"> Directives? RootTypeDef
+}
+
+RootTypeDef {
+  "{" RootOperationTypeDefinition+ "}"
+}
+
+SchemaExtension {
+  @specialize<Name, "extend"> @specialize<Name, "schema"> Directives? RootTypeDef
+}
+
+TypeExtension {
+  ScalarTypeExtension |
+  ObjectTypeExtension |
+  InterfaceTypeExtension |
+  UnionTypeExtension |
+  EnumTypeExtension |
+  InputObjectTypeExtension
+}
+
+ScalarTypeExtension {
+  @specialize<Name, "extend"> @specialize<Name, "scalar"> Name Directives
+}
+
+ObjectTypeExtension /* precedence: right 0 */ {
+  @specialize<Name, "extend"> @specialize<Name, "type"> Name ImplementsInterfaces? Directives? !typeDef FieldsDefinition |
+  @specialize<Name, "extend"> @specialize<Name, "type"> Name ImplementsInterfaces? Directives?
+}
+
+InterfaceTypeExtension /* precedence: right 0 */ {
+  @specialize<Name, "extend"> @specialize<Name, "interface"> Name ImplementsInterfaces? Directives? FieldsDefinition |
+  @specialize<Name, "extend"> @specialize<Name, "interface"> Name ImplementsInterfaces? Directives?
+}
+
+UnionTypeExtension /* precedence: right 0 */ {
+  @specialize<Name, "extend"> @specialize<Name, "union"> Name Directives? UnionMemberTypes |
+  @specialize<Name, "extend"> @specialize<Name, "union"> Name Directives?
+}
+
+EnumTypeExtension /* precedence: right 0 */ {
+  @specialize<Name, "extend"> @specialize<Name, "enum"> Name Directives? !typeDef EnumValuesDefinition |
+  @specialize<Name, "extend"> @specialize<Name, "enum"> Name Directives?
+}
+
+InputObjectTypeExtension /* precedence: right 0 */ {
+  @specialize<Name, "extend"> @specialize<Name, "input"> Name Directives? InputFieldsDefinition+ |
+  @specialize<Name, "extend"> @specialize<Name, "input"> Name Directives?
+}
+
+InputFieldsDefinition {
+  !fieldDef "{" InputValueDefinition+ "}"
+}
+
+EnumValuesDefinition {
+  !fieldDef "{" EnumValueDefinition+ "}"
+}
+
+EnumValueDefinition {
+  Description? EnumValue Directives?
+}
+
+ImplementsInterfaces {
+  ImplementsInterfaces "&" NamedType |
+  @specialize<Name, "implements"> "&"? NamedType
+}
+
+FieldsDefinition {
+  !fieldDef "{" FieldDefinition+ "}"
+}
+
+FieldDefinition {
+  Description? Name ArgumentsDefinition? ":" Type Directives?
+}
+
+ArgumentsDefinition {
+  "(" InputValueDefinition+ ")"
+}
+
+InputValueDefinition {
+  Description? Name ":" Type DefaultValue? Directives?
+}
+
+DefaultValue {
+  "=" Value
+}
+
+UnionMemberTypes {
+  UnionMemberTypes "|" NamedType |
+  "=" "|"? NamedType
+}
+
+RootOperationTypeDefinition {
+  OperationType ":" NamedType
+}
+
+OperationDefinition {
+  SelectionSet |
+  OperationType Name? VariableDefinitions? Directives? SelectionSet
+}
+
+TypeDefinition {
+  ScalarTypeDefinition |
+  ObjectTypeDefinition |
+  InterfaceTypeDefinition |
+  UnionTypeDefinition |
+  EnumTypeDefinition |
+  InputObjectTypeDefinition
+}
+
+ScalarTypeDefinition /* precedence: right 0 */ {
+  Description? @specialize<Name, "scalar"> Name Directives?
+}
+
+ObjectTypeDefinition /* precedence: right 0 */ {
+  Description? @specialize<Name, "type"> Name ImplementsInterfaces? Directives? FieldsDefinition?
+}
+
+InterfaceTypeDefinition /* precedence: right 0 */ {
+  Description? @specialize<Name, "interface"> Name ImplementsInterfaces? Directives? FieldsDefinition?
+}
+
+UnionTypeDefinition /* precedence: right 0 */ {
+  Description? @specialize<Name, "union"> Name Directives? UnionMemberTypes?
+}
+
+EnumTypeDefinition /* precedence: right 0 */ {
+  Description? @specialize<Name, "enum"> Name Directives? !typeDef EnumValuesDefinition?
+}
+
+InputObjectTypeDefinition /* precedence: right 0 */ {
+  Description? @specialize<Name, "input"> Name Directives? !typeDef InputFieldsDefinition?
+}
+
+VariableDefinitions {
+  "(" VariableDefinition+ ")"
+}
+
+VariableDefinition {
+  Variable ":" Type DefaultValue? Directives? Comma?
+}
+
+SelectionSet {
+  "{" Selection* "}"
+}
+
+Selection {
+  Field |
+  InlineFragment |
+  FragmentSpread
+}
+
+Field {
+  Alias? Name Arguments? Directive? SelectionSet?
+}
+
+Alias {
+  Name ":"
+}
+
+Arguments {
+  "(" Argument+ ")"
+}
+
+Argument {
+  Name ":" Value
+}
+
+Value {
+  Variable |
+  StringValue |
+  IntValue |
+  FloatValue |
+  BooleanValue |
+  NullValue |
+  EnumValue |
+  ListValue |
+  ObjectValue
+}
+
+Variable {
+  "$" Name
+}
+
+EnumValue {
+  Name
+}
+
+ListValue {
+  "[" Value* "]"
+}
+
+ObjectValue {
+  "{" ObjectField* "}"
+}
+
+ObjectField {
+  Name ":" Value Comma?
+}
+
+FragmentSpread {
+  "..." FragmentName Directives?
+}
+
+FragmentDefinition {
+  @specialize<Name, "fragment"> FragmentName TypeCondition Directives? SelectionSet
+}
+
+FragmentName {
+  Name
+}
+
+InlineFragment {
+  "..." TypeCondition? Directives? SelectionSet
+}
+
+TypeCondition {
+  @specialize<Name, "on"> NamedType
+}
+
+Directives {
+  Directive+
+}
+
+Directive {
+  "@" Name Arguments?
+}
+
+DirectiveDefinition /* precedence: right 1 */ {
+  Description? @specialize<Name, "directive"> "@" Name ArgumentsDefinition? @specialize<Name, "repeatable"> ? @specialize<Name, "on"> DirectiveLocations
+}
+
+DirectiveLocations {
+  DirectiveLocations "|" DirectiveLocation |
+  "|"? DirectiveLocation
+}
+
+DirectiveLocation {
+  ExecutableDirectiveLocation |
+  TypeSystemDirectiveLocation
+}
+
+Type {
+  NamedType |
+  ListType |
+  NonNullType
+}
+
+NamedType {
+  Name
+}
+
+ListType {
+  "[" Type "]"
+}
+
+NonNullType {
+  NamedType "!" |
+  ListType "!"
+}
+
+Description {
+  StringValue
+}
+
+OperationType {
+  @specialize<Name, "query"> 
+  | @specialize<Name, "mutation"> 
+  | @specialize<Name, "subscription">
+}
+
+BooleanValue {
+  @specialize<Name, "true">
+  | @specialize<Name, "false">
+}
+
+NullValue {
+  @specialize<Name, "null">
+}
+
+ExecutableDirectiveLocation {
+  @specialize<Name, "QUERY">
+  | @specialize<Name, "MUTATION">
+  | @specialize<Name, "SUBSCRIPTION">
+  | @specialize<Name, "FIELD"> 
+  | @specialize<Name, "FRAGMENT_DEFINITION">
+  | @specialize<Name, "FRAGMENT_SPREAD">
+  | @specialize<Name, "INLINE_FRAGMENT">
+  | @specialize<Name, "VARIABLE_DEFINITION">
+}
+
+TypeSystemDirectiveLocation {
+  @specialize<Name, "SCHEMA">
+  | @specialize<Name, "SCALAR">
+  | @specialize<Name, "OBJECT">
+  | @specialize<Name, "FIELD_DEFINITION">
+  | @specialize<Name, "ARGUMENT_DEFINITION">
+  | @specialize<Name, "INTERFACE">
+  | @specialize<Name, "UNION">
+  | @specialize<Name, "ENUM">
+  | @specialize<Name, "ENUM_VALUE">
+  | @specialize<Name, "INPUT_OBJECT">
+  | @specialize<Name, "INPUT_FIELD_DEFINITION">
+}
+
+@skip { Whitespace | Comment }
+
+@tokens {
+  Whitespace {
+    std.whitespace+
+  }
+  StringValue {
+    "\"\"\"" (!["] | "\\n" | "\"" "\""? !["])* "\"\"\"" | "\"" !["\\\n]* "\""
+  }
+  IntValue {
+    "-"? "0"
+    | "-"? std.digit+
+  }
+
+  FloatValue {
+    IntValue ("." std.digit+ | ("e" | "E") IntValue+) 
+  }
+
+  @precedence { IntValue, FloatValue }
+
+  Name {
+    $[_A-Za-z] $[_0-9A-Za-z]*
+  }
+  Comment {
+    "#" ![\n]*
+  }
+  Comma {
+    ","
+  }
+}
+
+@detectDelim

+ 1 - 0
packages/codemirror-lang-graphql/test/cases.txt

@@ -0,0 +1 @@
+# TODO: Write Lezer Tests

+ 17 - 0
packages/codemirror-lang-graphql/test/test.js

@@ -0,0 +1,17 @@
+import {GQLLanguage} from "../dist/index.js"
+import {fileTests} from "lezer-generator/dist/test"
+
+import * as fs from "fs"
+import * as path from "path"
+import { fileURLToPath } from 'url';
+let caseDir = path.dirname(fileURLToPath(import.meta.url))
+
+for (let file of fs.readdirSync(caseDir)) {
+  if (!/\.txt$/.test(file)) continue
+
+  let name = /^[^\.]*/.exec(file)[0]
+  describe(name, () => {
+    for (let {name, run} of fileTests(fs.readFileSync(path.join(caseDir, file), "utf8"), file))
+      it(name, () => run(GQLLanguage.parser))
+  })
+}

+ 12 - 0
packages/codemirror-lang-graphql/tsconfig.json

@@ -0,0 +1,12 @@
+{
+  "compilerOptions": {
+    "strict": true,
+    "target": "es6",
+    "module": "es2020",
+    "newLine": "lf",
+    "declaration": true,
+    "moduleResolution": "node",
+    "allowJs": true,
+  },
+  "include": ["src/*"]
+}

+ 6 - 3
packages/hoppscotch-app/assets/scss/styles.scss

@@ -32,8 +32,11 @@
 }
 
 ::selection {
-  @apply bg-accent;
-  @apply text-accentContrast;
+  @apply bg-divider;
+}
+
+.cm-focused {
+  @apply !outline-none;
 }
 
 input::placeholder,
@@ -286,7 +289,7 @@ pre.ace_editor {
   }
 }
 
-input[type="checkbox"] {
+input[type="checkbox"].checkbox {
   @apply hidden;
 
   &,

+ 46 - 0
packages/hoppscotch-app/assets/scss/themes.scss

@@ -52,6 +52,48 @@
   --editor-theme: "twilight";
 }
 
+@mixin dark-editor-theme {
+  --editor-type-color: theme("colors.purple.500");
+  --editor-name-color: theme("colors.blue.500");
+  --editor-operator-color: theme("colors.indigo.500");
+  --editor-invalid-color: theme("colors.red.500");
+  --editor-separator-color: theme("colors.gray.500");
+  --editor-meta-color: theme("colors.gray.500");
+  --editor-variable-color: theme("colors.green.500");
+  --editor-link-color: theme("colors.cyan.500");
+  --editor-process-color: theme("colors.gray.400");
+  --editor-constant-color: theme("colors.fuchsia.500");
+  --editor-keyword-color: theme("colors.pink.500");
+}
+
+@mixin light-editor-theme {
+  --editor-type-color: theme("colors.purple.600");
+  --editor-name-color: theme("colors.red.600");
+  --editor-operator-color: theme("colors.indigo.600");
+  --editor-invalid-color: theme("colors.red.600");
+  --editor-separator-color: theme("colors.gray.600");
+  --editor-meta-color: theme("colors.gray.600");
+  --editor-variable-color: theme("colors.green.600");
+  --editor-link-color: theme("colors.cyan.600");
+  --editor-process-color: theme("colors.blue.600");
+  --editor-constant-color: theme("colors.fuchsia.600");
+  --editor-keyword-color: theme("colors.pink.600");
+}
+
+@mixin black-editor-theme {
+  --editor-type-color: theme("colors.purple.400");
+  --editor-name-color: theme("colors.gray.400");
+  --editor-operator-color: theme("colors.indigo.400");
+  --editor-invalid-color: theme("colors.red.400");
+  --editor-separator-color: theme("colors.gray.400");
+  --editor-meta-color: theme("colors.gray.400");
+  --editor-variable-color: theme("colors.green.400");
+  --editor-link-color: theme("colors.cyan.400");
+  --editor-process-color: theme("colors.blue.400");
+  --editor-constant-color: theme("colors.fuchsia.400");
+  --editor-keyword-color: theme("colors.pink.400");
+}
+
 @mixin green-theme {
   --accent-color: theme("colors.green.500");
   --accent-light-color: theme("colors.green.400");
@@ -146,18 +188,22 @@
   @include base-theme;
   @include dark-theme;
   @include green-theme;
+  @include dark-editor-theme;
 }
 
 :root.light {
   @include light-theme;
+  @include light-editor-theme;
 }
 
 :root.dark {
   @include dark-theme;
+  @include dark-editor-theme;
 }
 
 :root.black {
   @include black-theme;
+  @include black-editor-theme;
 }
 
 :root[data-accent="blue"] {

+ 0 - 2
packages/hoppscotch-app/components/graphql/RequestOptions.vue

@@ -310,8 +310,6 @@ import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
 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"

+ 0 - 1
packages/hoppscotch-app/components/graphql/Sidebar.vue

@@ -254,7 +254,6 @@ import {
   setGQLURL,
   setGQLVariables,
 } from "~/newstore/GQLSession"
-import "~/helpers/editor/modes/graphql"
 
 function isTextFoundInGraphqlFieldObject(
   text: string,

+ 0 - 1
packages/hoppscotch-app/components/http/ImportCurl.vue

@@ -30,7 +30,6 @@ import {
   makeRESTRequest,
 } from "~/helpers/types/HoppRESTRequest"
 import { setRESTRequest } from "~/newstore/RESTSession"
-import "codemirror/mode/shell/shell"
 
 const {
   $toast,

+ 0 - 1
packages/hoppscotch-app/components/http/Parameters.vue

@@ -177,7 +177,6 @@ import {
   deleteAllRESTParams,
   setRESTParams,
 } from "~/newstore/RESTSession"
-import "codemirror/mode/yaml/yaml"
 
 const {
   $toast,

+ 0 - 1
packages/hoppscotch-app/components/http/PreRequestScript.vue

@@ -85,7 +85,6 @@
 import { reactive, ref, useContext } from "@nuxtjs/composition-api"
 import { usePreRequestScript } from "~/newstore/RESTSession"
 import snippets from "~/helpers/preRequestScriptSnippets"
-import "codemirror/mode/javascript/javascript"
 import { useCodemirror } from "~/helpers/editor/codemirror"
 import linter from "~/helpers/editor/linting/preRequest"
 import completer from "~/helpers/editor/completion/preRequest"

+ 0 - 5
packages/hoppscotch-app/components/http/RawBody.vue

@@ -72,11 +72,6 @@ import { useCodemirror } from "~/helpers/editor/codemirror"
 import { getEditorLangForMimeType } from "~/helpers/editorutils"
 import { pluckRef } from "~/helpers/utils/composables"
 import { useRESTRequestBody } from "~/newstore/RESTSession"
-import "codemirror/mode/yaml/yaml"
-import "codemirror/mode/xml/xml"
-import "codemirror/mode/css/css"
-import "codemirror/mode/htmlmixed/htmlmixed"
-import "codemirror/mode/javascript/javascript"
 
 const props = defineProps<{
   contentType: string

+ 0 - 1
packages/hoppscotch-app/components/http/Tests.vue

@@ -85,7 +85,6 @@
 import { reactive, ref, useContext } from "@nuxtjs/composition-api"
 import { useTestScript } from "~/newstore/RESTSession"
 import testSnippets from "~/helpers/testSnippets"
-import "codemirror/mode/javascript/javascript"
 import { useCodemirror } from "~/helpers/editor/codemirror"
 import linter from "~/helpers/editor/linting/testScript"
 import completer from "~/helpers/editor/completion/testScript"

+ 0 - 4
packages/hoppscotch-app/components/lenses/renderers/HTMLLensRenderer.vue

@@ -67,10 +67,6 @@
 import { computed, ref, useContext, reactive } from "@nuxtjs/composition-api"
 import { useCodemirror } from "~/helpers/editor/codemirror"
 import { copyToClipboard } from "~/helpers/utils/clipboard"
-import "codemirror/mode/xml/xml"
-import "codemirror/mode/javascript/javascript"
-import "codemirror/mode/css/css"
-import "codemirror/mode/htmlmixed/htmlmixed"
 import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
 
 const props = defineProps<{

+ 0 - 1
packages/hoppscotch-app/components/lenses/renderers/JSONLensRenderer.vue

@@ -147,7 +147,6 @@
 import { computed, ref, useContext, reactive } from "@nuxtjs/composition-api"
 import { useCodemirror } from "~/helpers/editor/codemirror"
 import { copyToClipboard } from "~/helpers/utils/clipboard"
-import "codemirror/mode/javascript/javascript"
 import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
 import jsonParse, { JSONObjectMember, JSONValue } from "~/helpers/jsonParse"
 import { getJSONOutlineAtPos } from "~/helpers/newOutline"

+ 0 - 1
packages/hoppscotch-app/components/lenses/renderers/XMLLensRenderer.vue

@@ -51,7 +51,6 @@
 import { computed, ref, useContext, reactive } from "@nuxtjs/composition-api"
 import { useCodemirror } from "~/helpers/editor/codemirror"
 import { copyToClipboard } from "~/helpers/utils/clipboard"
-import "codemirror/mode/xml/xml"
 import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
 
 const props = defineProps<{

+ 278 - 169
packages/hoppscotch-app/helpers/editor/codemirror.ts

@@ -1,215 +1,324 @@
-import CodeMirror from "codemirror"
-
-import "codemirror-theme-github/theme/github.css"
-import "codemirror/theme/base16-dark.css"
-import "codemirror/theme/tomorrow-night-bright.css"
-
-import "codemirror/lib/codemirror.css"
-import "codemirror/addon/lint/lint.css"
-import "codemirror/addon/dialog/dialog.css"
-import "codemirror/addon/hint/show-hint.css"
-
-import "codemirror/addon/fold/foldgutter.css"
-import "codemirror/addon/fold/foldgutter"
-import "codemirror/addon/fold/brace-fold"
-import "codemirror/addon/fold/comment-fold"
-import "codemirror/addon/fold/indent-fold"
-import "codemirror/addon/display/autorefresh"
-import "codemirror/addon/lint/lint"
-import "codemirror/addon/hint/show-hint"
-import "codemirror/addon/display/placeholder"
-import "codemirror/addon/edit/closebrackets"
-import "codemirror/addon/search/search"
-import "codemirror/addon/search/searchcursor"
-import "codemirror/addon/search/jump-to-line"
-import "codemirror/addon/dialog/dialog"
-import "codemirror/addon/selection/active-line"
-
-import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api"
-import { LinterDefinition } from "./linting/linter"
+import {
+  keymap,
+  EditorView,
+  ViewPlugin,
+  ViewUpdate,
+  placeholder,
+} from "@codemirror/view"
+import {
+  Extension,
+  EditorState,
+  Compartment,
+  EditorSelection,
+} from "@codemirror/state"
+import { Language, LanguageSupport } from "@codemirror/language"
+import { defaultKeymap } from "@codemirror/commands"
+import { Completion, autocompletion } from "@codemirror/autocomplete"
+import { linter } from "@codemirror/lint"
+
+import {
+  watch,
+  ref,
+  Ref,
+  onMounted,
+  onBeforeUnmount,
+} from "@nuxtjs/composition-api"
+
+import { javascriptLanguage } from "@codemirror/lang-javascript"
+import { jsonLanguage } from "@codemirror/lang-json"
+import { GQLLanguage } from "@hoppscotch/codemirror-lang-graphql"
+import { pipe } from "fp-ts/function"
+import * as O from "fp-ts/Option"
+import { isJSONContentType } from "../utils/contenttypes"
 import { Completer } from "./completion"
+import { LinterDefinition } from "./linting/linter"
+import { basicSetup, baseTheme, baseHighlightStyle } from "./themes/baseTheme"
+
+type ExtendedEditorConfig = {
+  mode: string
+  placeholder: string
+  readOnly: boolean
+  lineWrapping: boolean
+}
 
 type CodeMirrorOptions = {
-  extendedEditorConfig: Omit<CodeMirror.EditorConfiguration, "value">
+  extendedEditorConfig: Partial<ExtendedEditorConfig>
   linter: LinterDefinition | null
   completer: Completer | null
 }
 
-const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = {
-  autoRefresh: true,
-  lineNumbers: true,
-  foldGutter: true,
-  autoCloseBrackets: true,
-  gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
-  extraKeys: {
-    "Ctrl-Space": "autocomplete",
-  },
-  viewportMargin: Infinity,
-  styleActiveLine: true,
+const hoppCompleterExt = (completer: Completer): Extension => {
+  return autocompletion({
+    override: [
+      async (context) => {
+        // Expensive operation! Disable on bigger files ?
+        const text = context.state.doc.toJSON().join(context.state.lineBreak)
+
+        const line = context.state.doc.lineAt(context.pos)
+        const lineStart = line.from
+        const lineNo = line.number - 1
+        const ch = context.pos - lineStart
+
+        // Only do trigger on type when typing a word token, else stop (unless explicit)
+        if (!context.matchBefore(/\w+/) && !context.explicit)
+          return {
+            from: context.pos,
+            options: [],
+          }
+
+        const result = await completer(text, { line: lineNo, ch })
+
+        // Use more completion features ?
+        const completions =
+          result?.completions.map<Completion>((comp) => ({
+            label: comp.text,
+            detail: comp.meta,
+          })) ?? []
+
+        return {
+          from: context.state.wordAt(context.pos)?.from ?? context.pos,
+          options: completions,
+        }
+      },
+    ],
+  })
 }
 
-/**
- * A Vue composable to mount and use Codemirror
- *
- * NOTE: Make sure to import all the necessary Codemirror modules,
- * as this function doesn't import any other than the core
- * @param el Reference to the dom node to attach to
- * @param value Reference to value to read/write to
- * @param options CodeMirror options to pass
- */
-export function useCodemirror(
-  el: Ref<any | null>,
-  value: Ref<string>,
-  options: CodeMirrorOptions
-): { cm: Ref<CodeMirror.Position | null>; cursor: Ref<CodeMirror.Position> } {
-  const { $colorMode } = useContext() as any
+const hoppLinterExt = (hoppLinter: LinterDefinition): Extension => {
+  return linter(async (view) => {
+    // Requires full document scan, hence expensive on big files, force disable on big files ?
+    const linterResult = await hoppLinter(
+      view.state.doc.toJSON().join(view.state.lineBreak)
+    )
 
-  const cm = ref<CodeMirror.Editor | null>(null)
-  const cursor = ref<CodeMirror.Position>({ line: 0, ch: 0 })
+    return linterResult.map((result) => {
+      const startPos =
+        view.state.doc.line(result.from.line + 1).from + result.from.ch
+      const endPos = view.state.doc.line(result.to.line + 1).from + result.to.ch
 
-  const updateEditorConfig = () => {
-    Object.keys(options.extendedEditorConfig).forEach((key) => {
-      // Only update options which need updating
-      if (
-        cm.value &&
-        cm.value?.getOption(key as any) !==
-          (options.extendedEditorConfig as any)[key]
-      ) {
-        cm.value?.setOption(
-          key as any,
-          (options.extendedEditorConfig as any)[key]
-        )
+      return {
+        from: startPos,
+        to: endPos,
+        message: result.message,
+        severity: result.severity,
       }
     })
-  }
+  })
+}
 
-  const updateLinterConfig = () => {
-    if (options.linter) {
-      cm.value?.setOption("lint", options.linter)
-    }
-  }
+const hoppLang = (
+  language: Language,
+  linter?: LinterDefinition | undefined,
+  completer?: Completer | undefined
+) => {
+  const exts: Extension[] = []
 
-  const updateCompleterConfig = () => {
-    if (options.completer) {
-      cm.value?.setOption("hintOptions", {
-        completeSingle: false,
-        hint: async (editor: CodeMirror.Editor) => {
-          const pos = editor.getCursor()
-          const text = editor.getValue()
-
-          const token = editor.getTokenAt(pos)
-          // It's not a word token, so, just increment to skip to next
-          if (token.string.toUpperCase() === token.string.toLowerCase())
-            token.start += 1
-
-          const result = await options.completer!(text, pos)
-
-          if (!result) return null
-
-          return <CodeMirror.Hints>{
-            from: { line: pos.line, ch: token.start },
-            to: { line: pos.line, ch: token.end },
-            list: result.completions
-              .sort((a, b) => a.score - b.score)
-              .map((x) => x.text),
-          }
-        },
-      })
-    }
+  if (linter) exts.push(hoppLinterExt(linter))
+  if (completer) exts.push(hoppCompleterExt(completer))
+
+  return new LanguageSupport(language, exts)
+}
+
+const getLanguage = (langMime: string): Language | null => {
+  if (isJSONContentType(langMime)) {
+    return jsonLanguage
+  } else if (langMime === "application/javascript") {
+    return javascriptLanguage
+  } else if (langMime === "graphql") {
+    return GQLLanguage
   }
 
-  const initialize = () => {
-    if (!el.value) return
+  // None matched, so return null
+  return null
+}
 
-    cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG)
+const getEditorLanguage = (
+  langMime: string,
+  linter: LinterDefinition | undefined,
+  completer: Completer | undefined
+): Extension =>
+  pipe(
+    O.fromNullable(getLanguage(langMime)),
+    O.map((lang) => hoppLang(lang, linter, completer)),
+    O.getOrElseW(() => [])
+  )
 
-    cm.value.setValue(value.value)
+export function useCodemirror(
+  el: Ref<any | null>,
+  value: Ref<string>,
+  options: CodeMirrorOptions
+): { cursor: Ref<{ line: number; ch: number }> } {
+  const language = new Compartment()
+  const lineWrapping = new Compartment()
+  const placeholderConfig = new Compartment()
 
-    setTheme()
-    updateEditorConfig()
-    updateLinterConfig()
-    updateCompleterConfig()
+  const cachedCursor = ref({
+    line: 0,
+    ch: 0,
+  })
+  const cursor = ref({
+    line: 0,
+    ch: 0,
+  })
 
-    cm.value.on("change", (instance) => {
-      // External update propagation (via watchers) should be ignored
-      if (instance.getValue() !== value.value) {
-        value.value = instance.getValue()
-      }
-    })
+  const cachedValue = ref(value.value)
+
+  const view = ref<EditorView>()
+
+  const initView = (el: any) => {
+    view.value = new EditorView({
+      parent: el,
+      state: EditorState.create({
+        doc: value.value,
+        extensions: [
+          basicSetup,
+          baseTheme,
+          baseHighlightStyle,
+          ViewPlugin.fromClass(
+            class {
+              update(update: ViewUpdate) {
+                if (update.selectionSet) {
+                  const cursorPos = update.state.selection.main.head
+
+                  const line = update.state.doc.lineAt(cursorPos)
 
-    cm.value.on("cursorActivity", (instance) => {
-      cursor.value = instance.getCursor()
+                  cachedCursor.value = {
+                    line: line.number - 1,
+                    ch: cursorPos - line.from,
+                  }
+
+                  cursor.value = {
+                    line: cachedCursor.value.line,
+                    ch: cachedCursor.value.ch,
+                  }
+                }
+                if (update.docChanged) {
+                  // Expensive on big files ?
+                  cachedValue.value = update.state.doc
+                    .toJSON()
+                    .join(update.state.lineBreak)
+                  if (!options.extendedEditorConfig.readOnly)
+                    value.value = cachedValue.value
+                }
+              }
+            }
+          ),
+          EditorState.changeFilter.of(
+            () => !options.extendedEditorConfig.readOnly
+          ),
+          placeholderConfig.of(
+            placeholder(options.extendedEditorConfig.placeholder ?? "")
+          ),
+          language.of(
+            getEditorLanguage(
+              options.extendedEditorConfig.mode ?? "",
+              options.linter ?? undefined,
+              options.completer ?? undefined
+            )
+          ),
+          lineWrapping.of(
+            options.extendedEditorConfig.lineWrapping
+              ? [EditorView.lineWrapping]
+              : []
+          ),
+          keymap.of(defaultKeymap),
+        ],
+      }),
     })
   }
 
-  // Boot-up CodeMirror, set the value and listeners
   onMounted(() => {
-    initialize()
+    if (el.value) {
+      if (!view.value) initView(el.value)
+    }
   })
 
-  // Reinitialize if the target ref updates
   watch(el, () => {
-    if (cm.value) {
-      const parent = cm.value.getWrapperElement()
-      parent.remove()
-      cm.value = null
+    if (el.value) {
+      if (!view.value) initView(el.value)
+    } else {
+      view.value?.destroy()
+      view.value = undefined
     }
-    initialize()
   })
 
-  const setTheme = () => {
-    if (cm.value) {
-      cm.value?.setOption("theme", getThemeName($colorMode.value))
+  onBeforeUnmount(() => {
+    view.value?.destroy()
+  })
+
+  watch(value, (newVal) => {
+    if (cachedValue.value !== newVal) {
+      view.value?.dispatch({
+        filter: false,
+        changes: {
+          from: 0,
+          to: view.value.state.doc.length,
+          insert: newVal,
+        },
+      })
     }
-  }
+  })
 
-  const getThemeName = (mode: string) => {
-    switch (mode) {
-      case "system":
-        return "default"
-      case "light":
-        return "github"
-      case "dark":
-        return "base16-dark"
-      case "black":
-        return "tomorrow-night-bright"
-      default:
-        return "default"
+  watch(
+    () => [
+      options.extendedEditorConfig.mode,
+      options.linter,
+      options.completer,
+    ],
+    () => {
+      view.value?.dispatch({
+        effects: language.reconfigure(
+          getEditorLanguage(
+            (options.extendedEditorConfig.mode as any) ?? "",
+            options.linter ?? undefined,
+            options.completer ?? undefined
+          )
+        ),
+      })
     }
-  }
+  )
 
-  // If the editor properties are reactive, watch for updates
-  watch(() => options.extendedEditorConfig, updateEditorConfig, {
-    immediate: true,
-    deep: true,
-  })
-  watch(() => options.linter, updateLinterConfig, { immediate: true })
-  watch(() => options.completer, updateCompleterConfig, { immediate: true })
+  watch(
+    () => options.extendedEditorConfig.lineWrapping,
+    (newMode) => {
+      view.value?.dispatch({
+        effects: lineWrapping.reconfigure(
+          newMode ? [EditorView.lineWrapping] : []
+        ),
+      })
+    }
+  )
 
-  // Watch value updates
-  watch(value, (newVal) => {
-    // Check if we are mounted
-    if (cm.value) {
-      // Don't do anything on internal updates
-      if (cm.value.getValue() !== newVal) {
-        cm.value.setValue(newVal)
-      }
+  watch(
+    () => options.extendedEditorConfig.placeholder,
+    (newValue) => {
+      view.value?.dispatch({
+        effects: placeholderConfig.reconfigure(placeholder(newValue ?? "")),
+      })
     }
-  })
+  )
+
+  watch(cursor, (newPos) => {
+    if (view.value) {
+      if (
+        cachedCursor.value.line !== newPos.line ||
+        cachedCursor.value.ch !== newPos.ch
+      ) {
+        const line = view.value.state.doc.line(newPos.line + 1)
+        const selUpdate = EditorSelection.cursor(line.from + newPos.ch - 1)
+
+        view.value?.focus()
 
-  // Push cursor updates
-  watch(cursor, (value) => {
-    if (value !== cm.value?.getCursor()) {
-      cm.value?.focus()
-      cm.value?.setCursor(value)
+        view.value.dispatch({
+          scrollIntoView: true,
+          selection: selUpdate,
+          effects: EditorView.scrollTo.of(selUpdate),
+        })
+      }
     }
   })
 
-  // Watch color mode updates and update theme
-  watch(() => $colorMode.value, setTheme)
-
   return {
-    cm,
     cursor,
   }
 }

+ 0 - 80
packages/hoppscotch-app/helpers/editor/modes/graphql.ts

@@ -1,80 +0,0 @@
-/**
- *  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<any>["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<any> & {
-    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)
-}

+ 233 - 0
packages/hoppscotch-app/helpers/editor/themes/baseTheme.ts

@@ -0,0 +1,233 @@
+import {
+  EditorView,
+  keymap,
+  highlightSpecialChars,
+  highlightActiveLine,
+} from "@codemirror/view"
+import {
+  HighlightStyle,
+  tags as t,
+  defaultHighlightStyle,
+} from "@codemirror/highlight"
+import { foldKeymap, foldGutter } from "@codemirror/fold"
+
+import { Extension, EditorState } from "@codemirror/state"
+import { history, historyKeymap } from "@codemirror/history"
+import { indentOnInput } from "@codemirror/language"
+import { lineNumbers, highlightActiveLineGutter } from "@codemirror/gutter"
+import { defaultKeymap } from "@codemirror/commands"
+import { bracketMatching } from "@codemirror/matchbrackets"
+import { closeBrackets, closeBracketsKeymap } from "@codemirror/closebrackets"
+import { searchKeymap, highlightSelectionMatches } from "@codemirror/search"
+import { autocompletion, completionKeymap } from "@codemirror/autocomplete"
+import { commentKeymap } from "@codemirror/comment"
+import { rectangularSelection } from "@codemirror/rectangular-selection"
+import { lintKeymap } from "@codemirror/lint"
+
+export const baseTheme = EditorView.theme({
+  "&": {
+    fontSize: "var(--body-font-size)",
+  },
+  ".cm-content": {
+    caretColor: "var(--secondary-light-color)",
+    fontFamily: "var(--font-mono)",
+    backgroundColor: "var(--primary-color)",
+  },
+  ".cm-cursor": {
+    borderColor: "var(--secondary-color)",
+  },
+  ".cm-selectionBackground, .cm-content ::selection, .cm-line ::selection": {
+    backgroundColor: "var(--divider-color)",
+  },
+  ".cm-panels": {
+    backgroundColor: "var(--primary-light-color)",
+    color: "var(--secondary-light-color)",
+  },
+  ".cm-panels.cm-panels-top": {
+    borderBottom: "1px solid var(--divider-light-color)",
+  },
+  ".cm-panels.cm-panels-bottom": {
+    borderTop: "1px solid var(--divider-light-color)",
+  },
+  ".cm-search": {
+    display: "flex",
+    alignItems: "center",
+    flexWrap: "nowrap",
+    flexShrink: 0,
+    overflow: "auto",
+  },
+  ".cm-search label": {
+    display: "inline-flex",
+    alignItems: "center",
+  },
+  ".cm-textfield": {
+    backgroundColor: "var(--primary-dark-color)",
+    color: "var(--secondary-light-color)",
+    borderColor: "var(--divider-light-color)",
+    borderRadius: "3px",
+  },
+  ".cm-button": {
+    backgroundColor: "var(--primary-dark-color)",
+    color: "var(--secondary-light-color)",
+    backgroundImage: "none",
+    border: "none",
+  },
+  ".cm-tooltip": {
+    backgroundColor: "var(--primary-dark-color)",
+    color: "var(--secondary-light-color)",
+    border: "none",
+    borderRadius: "3px",
+  },
+  ".cm-completionLabel": {
+    color: "var(--secondary-color)",
+  },
+  ".cm-tooltip.cm-tooltip-autocomplete > ul": {
+    fontFamily: "var(--font-mono)",
+  },
+  ".cm-tooltip-autocomplete ul li[aria-selected]": {
+    backgroundColor: "var(--accent-dark-color)",
+    color: "var(--accent-contrast-color)",
+  },
+  ".cm-tooltip-autocomplete ul li[aria-selected] .cm-completionLabel": {
+    color: "var(--accent-contrast-color)",
+  },
+  ".cm-activeLine": { backgroundColor: "var(--primary-light-color)" },
+  ".cm-searchMatch": {
+    outline: "1px solid var(--accent-dark-color)",
+  },
+  ".cm-selectionMatch": {
+    outline: "1px solid var(--accent-dark-color)",
+  },
+  ".cm-matchingBracket, .cm-nonmatchingBracket": {
+    backgroundColor: "var(--divider-color)",
+    outline: "1px solid var(--accent-dark-color)",
+  },
+  ".cm-gutters": {
+    fontFamily: "var(--font-mono)",
+    backgroundColor: "var(--primary-color)",
+    borderColor: "var(--divider-light-color)",
+  },
+  ".cm-lineNumbers": {
+    minWidth: "3em",
+    color: "var(--secondary-light-color)",
+  },
+  ".cm-foldGutter": {
+    minWidth: "2em",
+    color: "var(--secondary-light-color)",
+  },
+  ".cm-foldGutter .cm-gutterElement": {
+    textAlign: "center",
+  },
+  ".cm-line": {
+    paddingLeft: "0.5em",
+    paddingRight: "0.5em",
+    color: "var(--secondary-dark-color)",
+  },
+  ".cm-activeLineGutter": {
+    backgroundColor: "var(--primary-dark-color)",
+  },
+  ".cm-scroller::-webkit-scrollbar": {
+    display: "none",
+  },
+})
+
+const editorTypeColor = "var(--editor-type-color)"
+const editorNameColor = "var(--editor-name-color)"
+const editorOperatorColor = "var(--editor-operator-color)"
+const editorInvalidColor = "var(--editor-invalid-color)"
+const editorSeparatorColor = "var(--editor-separator-color)"
+const editorMetaColor = "var(--editor-meta-color)"
+const editorVariableColor = "var(--editor-variable-color)"
+const editorLinkColor = "var(--editor-link-color)"
+const editorProcessColor = "var(--editor-process-color)"
+const editorConstantColor = "var(--editor-constant-color)"
+const editorKeywordColor = "var(--editor-keyword-color)"
+
+export const baseHighlightStyle = HighlightStyle.define([
+  { tag: t.keyword, color: editorKeywordColor },
+  {
+    tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
+    color: editorNameColor,
+  },
+  {
+    tag: [t.function(t.variableName), t.labelName],
+    color: editorVariableColor,
+  },
+  {
+    tag: [t.color, t.constant(t.name), t.standard(t.name)],
+    color: editorConstantColor,
+  },
+  { tag: [t.definition(t.name), t.separator], color: editorSeparatorColor },
+  {
+    tag: [
+      t.typeName,
+      t.className,
+      t.number,
+      t.changed,
+      t.annotation,
+      t.modifier,
+      t.self,
+      t.namespace,
+    ],
+    color: editorTypeColor,
+  },
+  {
+    tag: [
+      t.operator,
+      t.operatorKeyword,
+      t.url,
+      t.escape,
+      t.regexp,
+      t.link,
+      t.special(t.string),
+    ],
+    color: editorOperatorColor,
+  },
+  { tag: [t.meta, t.comment], color: editorMetaColor },
+  { tag: t.strong, fontWeight: "bold" },
+  { tag: t.emphasis, fontStyle: "italic" },
+  { tag: t.strikethrough, textDecoration: "line-through" },
+  { tag: t.link, color: editorLinkColor, textDecoration: "underline" },
+  { tag: t.heading, fontWeight: "bold", color: editorNameColor },
+  {
+    tag: [t.atom, t.bool, t.special(t.variableName)],
+    color: editorConstantColor,
+  },
+  {
+    tag: [t.processingInstruction, t.string, t.inserted],
+    color: editorProcessColor,
+  },
+  { tag: t.invalid, color: editorInvalidColor },
+])
+
+const baseFoldStyle = foldGutter({
+  openText: "▾",
+  closedText: "▸",
+})
+
+export const basicSetup: Extension = [
+  lineNumbers(),
+  highlightActiveLineGutter(),
+  highlightSpecialChars(),
+  history(),
+  baseFoldStyle,
+  EditorState.allowMultipleSelections.of(true),
+  indentOnInput(),
+  defaultHighlightStyle.fallback,
+  bracketMatching(),
+  closeBrackets(),
+  autocompletion(),
+  rectangularSelection(),
+  highlightActiveLine(),
+  highlightSelectionMatches(),
+  keymap.of([
+    ...closeBracketsKeymap,
+    ...defaultKeymap,
+    ...searchKeymap,
+    ...historyKeymap,
+    ...foldKeymap,
+    ...commentKeymap,
+    ...completionKeymap,
+    ...lintKeymap,
+  ]),
+]

+ 19 - 2
packages/hoppscotch-app/package.json

@@ -34,6 +34,25 @@
   },
   "dependencies": {
     "@apollo/client": "^3.4.17",
+    "@codemirror/autocomplete": "^0.19.4",
+    "@codemirror/closebrackets": "^0.19.0",
+    "@codemirror/commands": "^0.19.5",
+    "@codemirror/comment": "^0.19.0",
+    "@codemirror/fold": "^0.19.1",
+    "@codemirror/gutter": "^0.19.4",
+    "@codemirror/highlight": "^0.19.0",
+    "@codemirror/history": "^0.19.0",
+    "@codemirror/lang-javascript": "^0.19.2",
+    "@codemirror/lang-json": "^0.19.1",
+    "@codemirror/language": "^0.19.3",
+    "@codemirror/lint": "^0.19.2",
+    "@codemirror/matchbrackets": "^0.19.3",
+    "@codemirror/rectangular-selection": "^0.19.1",
+    "@codemirror/search": "^0.19.2",
+    "@codemirror/state": "^0.19.3",
+    "@codemirror/text": "^0.19.5",
+    "@codemirror/view": "^0.19.12",
+    "@hoppscotch/codemirror-lang-graphql": "workspace:^0.1.0",
     "@hoppscotch/js-sandbox": "workspace:^1.0.0",
     "@nuxtjs/axios": "^5.13.6",
     "@nuxtjs/composition-api": "^0.30.0",
@@ -48,8 +67,6 @@
     "acorn": "^8.5.0",
     "acorn-walk": "^8.2.0",
     "axios": "^0.24.0",
-    "codemirror": "^5.63.3",
-    "codemirror-theme-github": "^1.0.0",
     "core-js": "^3.19.1",
     "esprima": "^4.0.1",
     "firebase": "^9.4.1",

File diff suppressed because it is too large
+ 435 - 77
pnpm-lock.yaml


Some files were not shown because too many files changed in this diff