Merge branch 'feat/codemirror-6'
This commit is contained in:
4
packages/codemirror-lang-graphql/.gitignore
vendored
Normal file
4
packages/codemirror-lang-graphql/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/node_modules
|
||||
package-lock.json
|
||||
/dist
|
||||
/src/*.d.ts
|
||||
5
packages/codemirror-lang-graphql/.npmignore
Normal file
5
packages/codemirror-lang-graphql/.npmignore
Normal file
@@ -0,0 +1,5 @@
|
||||
/src
|
||||
/test
|
||||
/node_modules
|
||||
rollup.config.js
|
||||
tsconfig.json
|
||||
1
packages/codemirror-lang-graphql/README.md
Normal file
1
packages/codemirror-lang-graphql/README.md
Normal file
@@ -0,0 +1 @@
|
||||
A [CodeMirror 6](https://codemirror.net/6) language plugin for GraphQL
|
||||
32
packages/codemirror-lang-graphql/package.json
Normal file
32
packages/codemirror-lang-graphql/package.json
Normal file
@@ -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
packages/codemirror-lang-graphql/rollup.config.js
Normal file
12
packages/codemirror-lang-graphql/rollup.config.js
Normal file
@@ -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
packages/codemirror-lang-graphql/src/index.js
Normal file
43
packages/codemirror-lang-graphql/src/index.js
Normal file
@@ -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
packages/codemirror-lang-graphql/src/syntax.grammar
Normal file
372
packages/codemirror-lang-graphql/src/syntax.grammar
Normal file
@@ -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
packages/codemirror-lang-graphql/test/cases.txt
Normal file
1
packages/codemirror-lang-graphql/test/cases.txt
Normal file
@@ -0,0 +1 @@
|
||||
# TODO: Write Lezer Tests
|
||||
17
packages/codemirror-lang-graphql/test/test.js
Normal file
17
packages/codemirror-lang-graphql/test/test.js
Normal file
@@ -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
packages/codemirror-lang-graphql/tsconfig.json
Normal file
12
packages/codemirror-lang-graphql/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "es6",
|
||||
"module": "es2020",
|
||||
"newLine": "lf",
|
||||
"declaration": true,
|
||||
"moduleResolution": "node",
|
||||
"allowJs": true,
|
||||
},
|
||||
"include": ["src/*"]
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
&,
|
||||
|
||||
@@ -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"] {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -254,7 +254,6 @@ import {
|
||||
setGQLURL,
|
||||
setGQLVariables,
|
||||
} from "~/newstore/GQLSession"
|
||||
import "~/helpers/editor/modes/graphql"
|
||||
|
||||
function isTextFoundInGraphqlFieldObject(
|
||||
text: string,
|
||||
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
makeRESTRequest,
|
||||
} from "~/helpers/types/HoppRESTRequest"
|
||||
import { setRESTRequest } from "~/newstore/RESTSession"
|
||||
import "codemirror/mode/shell/shell"
|
||||
|
||||
const {
|
||||
$toast,
|
||||
|
||||
@@ -177,7 +177,6 @@ import {
|
||||
deleteAllRESTParams,
|
||||
setRESTParams,
|
||||
} from "~/newstore/RESTSession"
|
||||
import "codemirror/mode/yaml/yaml"
|
||||
|
||||
const {
|
||||
$toast,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -1,215 +1,324 @@
|
||||
import CodeMirror from "codemirror"
|
||||
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 "codemirror-theme-github/theme/github.css"
|
||||
import "codemirror/theme/base16-dark.css"
|
||||
import "codemirror/theme/tomorrow-night-bright.css"
|
||||
import {
|
||||
watch,
|
||||
ref,
|
||||
Ref,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
} from "@nuxtjs/composition-api"
|
||||
|
||||
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 { 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
|
||||
*/
|
||||
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)
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
return {
|
||||
from: startPos,
|
||||
to: endPos,
|
||||
message: result.message,
|
||||
severity: result.severity,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const hoppLang = (
|
||||
language: Language,
|
||||
linter?: LinterDefinition | undefined,
|
||||
completer?: Completer | undefined
|
||||
) => {
|
||||
const exts: Extension[] = []
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// None matched, so return null
|
||||
return null
|
||||
}
|
||||
|
||||
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(() => [])
|
||||
)
|
||||
|
||||
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
|
||||
): { cursor: Ref<{ line: number; ch: number }> } {
|
||||
const language = new Compartment()
|
||||
const lineWrapping = new Compartment()
|
||||
const placeholderConfig = new Compartment()
|
||||
|
||||
const cm = ref<CodeMirror.Editor | null>(null)
|
||||
const cursor = ref<CodeMirror.Position>({ line: 0, ch: 0 })
|
||||
const cachedCursor = ref({
|
||||
line: 0,
|
||||
ch: 0,
|
||||
})
|
||||
const cursor = ref({
|
||||
line: 0,
|
||||
ch: 0,
|
||||
})
|
||||
|
||||
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]
|
||||
)
|
||||
}
|
||||
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)
|
||||
|
||||
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),
|
||||
],
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
const updateLinterConfig = () => {
|
||||
if (options.linter) {
|
||||
cm.value?.setOption("lint", options.linter)
|
||||
onMounted(() => {
|
||||
if (el.value) {
|
||||
if (!view.value) initView(el.value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const updateCompleterConfig = () => {
|
||||
if (options.completer) {
|
||||
cm.value?.setOption("hintOptions", {
|
||||
completeSingle: false,
|
||||
hint: async (editor: CodeMirror.Editor) => {
|
||||
const pos = editor.getCursor()
|
||||
const text = editor.getValue()
|
||||
watch(el, () => {
|
||||
if (el.value) {
|
||||
if (!view.value) initView(el.value)
|
||||
} else {
|
||||
view.value?.destroy()
|
||||
view.value = undefined
|
||||
}
|
||||
})
|
||||
|
||||
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
|
||||
onBeforeUnmount(() => {
|
||||
view.value?.destroy()
|
||||
})
|
||||
|
||||
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),
|
||||
}
|
||||
watch(value, (newVal) => {
|
||||
if (cachedValue.value !== newVal) {
|
||||
view.value?.dispatch({
|
||||
filter: false,
|
||||
changes: {
|
||||
from: 0,
|
||||
to: view.value.state.doc.length,
|
||||
insert: newVal,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const initialize = () => {
|
||||
if (!el.value) return
|
||||
|
||||
cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG)
|
||||
|
||||
cm.value.setValue(value.value)
|
||||
|
||||
setTheme()
|
||||
updateEditorConfig()
|
||||
updateLinterConfig()
|
||||
updateCompleterConfig()
|
||||
|
||||
cm.value.on("change", (instance) => {
|
||||
// External update propagation (via watchers) should be ignored
|
||||
if (instance.getValue() !== value.value) {
|
||||
value.value = instance.getValue()
|
||||
}
|
||||
})
|
||||
|
||||
cm.value.on("cursorActivity", (instance) => {
|
||||
cursor.value = instance.getCursor()
|
||||
})
|
||||
}
|
||||
|
||||
// Boot-up CodeMirror, set the value and listeners
|
||||
onMounted(() => {
|
||||
initialize()
|
||||
})
|
||||
|
||||
// Reinitialize if the target ref updates
|
||||
watch(el, () => {
|
||||
if (cm.value) {
|
||||
const parent = cm.value.getWrapperElement()
|
||||
parent.remove()
|
||||
cm.value = null
|
||||
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
|
||||
)
|
||||
),
|
||||
})
|
||||
}
|
||||
initialize()
|
||||
})
|
||||
)
|
||||
|
||||
const setTheme = () => {
|
||||
if (cm.value) {
|
||||
cm.value?.setOption("theme", getThemeName($colorMode.value))
|
||||
watch(
|
||||
() => options.extendedEditorConfig.lineWrapping,
|
||||
(newMode) => {
|
||||
view.value?.dispatch({
|
||||
effects: lineWrapping.reconfigure(
|
||||
newMode ? [EditorView.lineWrapping] : []
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
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.placeholder,
|
||||
(newValue) => {
|
||||
view.value?.dispatch({
|
||||
effects: placeholderConfig.reconfigure(placeholder(newValue ?? "")),
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 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(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)
|
||||
|
||||
// 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)
|
||||
view.value?.focus()
|
||||
|
||||
view.value.dispatch({
|
||||
scrollIntoView: true,
|
||||
selection: selUpdate,
|
||||
effects: EditorView.scrollTo.of(selUpdate),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Push cursor updates
|
||||
watch(cursor, (value) => {
|
||||
if (value !== cm.value?.getCursor()) {
|
||||
cm.value?.focus()
|
||||
cm.value?.setCursor(value)
|
||||
}
|
||||
})
|
||||
|
||||
// Watch color mode updates and update theme
|
||||
watch(() => $colorMode.value, setTheme)
|
||||
|
||||
return {
|
||||
cm,
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
packages/hoppscotch-app/helpers/editor/themes/baseTheme.ts
Normal file
233
packages/hoppscotch-app/helpers/editor/themes/baseTheme.ts
Normal file
@@ -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,
|
||||
]),
|
||||
]
|
||||
@@ -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",
|
||||
|
||||
732
pnpm-lock.yaml
generated
732
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user