From 8c9cd079b783e75cf889668069178b9d97af74ea Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 31 Aug 2021 00:03:07 +0530 Subject: [PATCH 01/65] feat: init codemirror --- components/smart/CodeMirror.vue | 85 +++++++++++++++++++++++++++++++++ package-lock.json | 11 +++++ package.json | 1 + 3 files changed, 97 insertions(+) create mode 100644 components/smart/CodeMirror.vue diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue new file mode 100644 index 00000000..f4dfc315 --- /dev/null +++ b/components/smart/CodeMirror.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/package-lock.json b/package-lock.json index 13bea05f..bb3502c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "ace-builds": "^1.4.12", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", + "codemirror": "^5.62.3", "core-js": "^3.16.4", "esprima": "^4.0.1", "firebase": "^9.0.0", @@ -13065,6 +13066,11 @@ "node": ">=0.10.0" } }, + "node_modules/codemirror": { + "version": "5.62.3", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", + "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" + }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -46032,6 +46038,11 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "codemirror": { + "version": "5.62.3", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", + "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", diff --git a/package.json b/package.json index 1bfe4b70..867955c4 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "ace-builds": "^1.4.12", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", + "codemirror": "^5.62.3", "core-js": "^3.16.4", "esprima": "^4.0.1", "firebase": "^9.0.0", From 15373be63e6a73964128fd382a097aeb1e9d5b85 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 31 Aug 2021 10:47:00 +0530 Subject: [PATCH 02/65] refactor: ts codemirror --- components/smart/CodeMirror.vue | 108 +++++++++++++++----------------- package-lock.json | 49 +++++++++++++++ package.json | 1 + 3 files changed, 102 insertions(+), 56 deletions(-) diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index f4dfc315..f29fe92a 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -2,84 +2,80 @@
- - diff --git a/package-lock.json b/package-lock.json index bb3502c9..f309be5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "@nuxtjs/stylelint-module": "^4.0.0", "@nuxtjs/svg": "^0.2.0", "@testing-library/jest-dom": "^5.14.1", + "@types/codemirror": "^5.60.2", "@types/cookie": "^0.4.1", "@types/lodash": "^4.14.172", "@types/lunr": "^2.3.4", @@ -7891,6 +7892,15 @@ "node": ">=0.10.0" } }, + "node_modules/@types/codemirror": { + "version": "5.60.2", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.2.tgz", + "integrity": "sha512-tk8YxckrdU49GaJYRKxdzzzXrTlyT2nQGnobb8rAk34jt+kYXOxPKGqNgr7SJpl5r6YGaRD4CDfqiL+6A+/z7w==", + "dev": true, + "dependencies": { + "@types/tern": "*" + } + }, "node_modules/@types/component-emitter": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", @@ -7940,6 +7950,12 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" }, + "node_modules/@types/estree": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "dev": true + }, "node_modules/@types/etag": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@types/etag/-/etag-1.8.0.tgz", @@ -8330,6 +8346,15 @@ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==" }, + "node_modules/@types/tern": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", + "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/terser-webpack-plugin": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@types/terser-webpack-plugin/-/terser-webpack-plugin-4.2.1.tgz", @@ -41813,6 +41838,15 @@ } } }, + "@types/codemirror": { + "version": "5.60.2", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.2.tgz", + "integrity": "sha512-tk8YxckrdU49GaJYRKxdzzzXrTlyT2nQGnobb8rAk34jt+kYXOxPKGqNgr7SJpl5r6YGaRD4CDfqiL+6A+/z7w==", + "dev": true, + "requires": { + "@types/tern": "*" + } + }, "@types/component-emitter": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", @@ -41862,6 +41896,12 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" }, + "@types/estree": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "dev": true + }, "@types/etag": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@types/etag/-/etag-1.8.0.tgz", @@ -42252,6 +42292,15 @@ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==" }, + "@types/tern": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", + "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, "@types/terser-webpack-plugin": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@types/terser-webpack-plugin/-/terser-webpack-plugin-4.2.1.tgz", diff --git a/package.json b/package.json index 867955c4..59182f6a 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "@nuxtjs/stylelint-module": "^4.0.0", "@nuxtjs/svg": "^0.2.0", "@testing-library/jest-dom": "^5.14.1", + "@types/codemirror": "^5.60.2", "@types/cookie": "^0.4.1", "@types/lodash": "^4.14.172", "@types/lunr": "^2.3.4", From e2b1c83698614e5a5a49da845c5c5f1f15b8b7e8 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 31 Aug 2021 16:10:35 +0530 Subject: [PATCH 03/65] feat: line wrap, auto close brackets, placeholder on codemirror --- components/smart/CodeMirror.vue | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index f29fe92a..292732f1 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -9,14 +9,10 @@ import "codemirror/lib/codemirror.css" import "codemirror/theme/juejin.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/mode/javascript/javascript" +import "codemirror/addon/selection/active-line" +import "codemirror/addon/edit/closebrackets" +import "codemirror/addon/display/placeholder" import { onMounted, ref, watch } from "@nuxtjs/composition-api" @@ -25,6 +21,8 @@ const DEFAULT_THEME = "juejin" const props = defineProps<{ value: string mode: string + placeholder: string + wrap: boolean }>() const emit = defineEmits<{ @@ -53,14 +51,17 @@ onMounted(() => { cm.value = Codemirror(editor.value, { value: props.value, mode: props.mode, + lineWrapping: props.wrap, + placeholder: props.placeholder, autoRefresh: true, lineNumbers: true, - foldGutter: true, + styleActiveLine: true, + autoCloseBrackets: true, theme: DEFAULT_THEME, - gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], + gutters: ["CodeMirror-linenumbers"], }) - cm.value?.on("change", (instance) => { - const val = instance.getValue() + cm.value?.on("change", (cm) => { + const val = cm.getValue() emit("input", val) }) }) @@ -73,6 +74,7 @@ onMounted(() => { @apply border-dividerLight; @apply w-full; @apply h-auto; + @apply font-mono; } .CodeMirror-scroll { From 86489d95c2c3dcdfc05006cb222adcf69eb6fe7a Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Tue, 31 Aug 2021 22:20:42 +0530 Subject: [PATCH 04/65] refactor: extract common codemirror logic out to composable --- components/smart/CodeMirror.vue | 49 ++++------------------- helpers/editor/codemirror.ts | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 helpers/editor/codemirror.ts diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 292732f1..6068778c 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -3,20 +3,10 @@ diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts new file mode 100644 index 00000000..9a282979 --- /dev/null +++ b/helpers/editor/codemirror.ts @@ -0,0 +1,69 @@ +import CodeMirror from "codemirror" + +import "codemirror/theme/juejin.css" + +import "codemirror/lib/codemirror.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 { watch, onMounted, ref, Ref } from "@nuxtjs/composition-api" + +const DEFAULT_THEME = "juejin" + +const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { + theme: DEFAULT_THEME, + autoRefresh: true, + lineNumbers: true, + foldGutter: true, + gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], +} + +/** + * 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, + value: Ref, + options: CodeMirror.EditorConfiguration +) { + const cm = ref(null) + + // Boot-up CodeMirror, set the value and listeners + onMounted(() => { + cm.value = CodeMirror(el.value!, { ...DEFAULT_EDITOR_CONFIG, ...options }) + cm.value.setValue(value.value) + + cm.value.on("change", (instance) => { + // External update propagation (via watchers) should be ignored + if (instance.getValue() !== value.value) { + value.value = instance.getValue() + } + }) + }) + + // 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) + } + } + }) + + return { + cm, + } +} From e47ad946660c7f2c70d1994e19bd87662e4b00ae Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 1 Sep 2021 16:34:02 +0530 Subject: [PATCH 05/65] refactor: add types for esprima --- package-lock.json | 19 +++++++++++++++++++ package.json | 1 + 2 files changed, 20 insertions(+) diff --git a/package-lock.json b/package-lock.json index ee7fdab3..a456b63a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "@testing-library/jest-dom": "^5.14.1", "@types/codemirror": "^5.60.2", "@types/cookie": "^0.4.1", + "@types/esprima": "^4.0.3", "@types/lodash": "^4.14.172", "@types/lunr": "^2.3.4", "@types/splitpanes": "^2.2.1", @@ -7951,6 +7952,15 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" }, + "node_modules/@types/esprima": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/esprima/-/esprima-4.0.3.tgz", + "integrity": "sha512-jo14dIWVVtF0iMsKkYek6++4cWJjwpvog+rchLulwgFJGTXqIeTdCOvY0B3yMLTaIwMcKCdJ6mQbSR6wYHy98A==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", @@ -41912,6 +41922,15 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" }, + "@types/esprima": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/esprima/-/esprima-4.0.3.tgz", + "integrity": "sha512-jo14dIWVVtF0iMsKkYek6++4cWJjwpvog+rchLulwgFJGTXqIeTdCOvY0B3yMLTaIwMcKCdJ6mQbSR6wYHy98A==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, "@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", diff --git a/package.json b/package.json index f1b22c2d..45a29101 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "@testing-library/jest-dom": "^5.14.1", "@types/codemirror": "^5.60.2", "@types/cookie": "^0.4.1", + "@types/esprima": "^4.0.3", "@types/lodash": "^4.14.172", "@types/lunr": "^2.3.4", "@types/splitpanes": "^2.2.1", From 52765568379d6c2620275ad5715cd3d9ca1c867b Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 1 Sep 2021 16:41:14 +0530 Subject: [PATCH 06/65] feat: codemirror linting system --- components/smart/CodeMirror.vue | 24 +++++++++++++++++------- helpers/editor/codemirror.ts | 19 +++++++++++++++++-- helpers/editor/linting/linter.ts | 7 +++++++ helpers/editor/utils.ts | 23 +++++++++++++++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 helpers/editor/linting/linter.ts create mode 100644 helpers/editor/utils.ts diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 6068778c..3cf60203 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -7,13 +7,20 @@ import "codemirror/mode/javascript/javascript" import { ref, watch } from "@nuxtjs/composition-api" import { useCodemirror } from "~/helpers/editor/codemirror" +import { LinterDefinition } from "~/helpers/editor/linting/linter" -const props = defineProps<{ - value: string - mode: string - placeholder: string - wrap: boolean -}>() +const props = withDefaults( + defineProps<{ + value: string + mode: string + placeholder: string + wrap: boolean + linter: LinterDefinition | null + }>(), + { + linter: null as any, + } +) const emit = defineEmits<{ (e: "input", value: string): void @@ -30,7 +37,10 @@ watch(value, (val) => emit("input", val)) const editor = ref(null) useCodemirror(editor, value, { - mode: props.mode, + extendedEditorConfig: { + mode: props.mode, + }, + linter: props.linter, }) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 9a282979..49969267 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -3,6 +3,7 @@ import CodeMirror from "codemirror" import "codemirror/theme/juejin.css" import "codemirror/lib/codemirror.css" +import "codemirror/addon/lint/lint.css" import "codemirror/addon/fold/foldgutter.css" import "codemirror/addon/fold/foldgutter" @@ -10,11 +11,18 @@ 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 { watch, onMounted, ref, Ref } from "@nuxtjs/composition-api" +import { LinterDefinition } from "./linting/linter" const DEFAULT_THEME = "juejin" +type CodeMirrorOptions = { + extendedEditorConfig: CodeMirror.EditorConfiguration + linter: LinterDefinition | null +} + const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { theme: DEFAULT_THEME, autoRefresh: true, @@ -35,15 +43,22 @@ const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { export function useCodemirror( el: Ref, value: Ref, - options: CodeMirror.EditorConfiguration + options: CodeMirrorOptions ) { const cm = ref(null) // Boot-up CodeMirror, set the value and listeners onMounted(() => { - cm.value = CodeMirror(el.value!, { ...DEFAULT_EDITOR_CONFIG, ...options }) + cm.value = CodeMirror(el.value!, { + ...DEFAULT_EDITOR_CONFIG, + ...options.extendedEditorConfig, + }) cm.value.setValue(value.value) + if (options.linter) { + cm.value.setOption("lint", options.linter) + } + cm.value.on("change", (instance) => { // External update propagation (via watchers) should be ignored if (instance.getValue() !== value.value) { diff --git a/helpers/editor/linting/linter.ts b/helpers/editor/linting/linter.ts new file mode 100644 index 00000000..704270cb --- /dev/null +++ b/helpers/editor/linting/linter.ts @@ -0,0 +1,7 @@ +export type LinterResult = { + message: string + severity: "warning" | "error" + from: { line: number; ch: number } + to: { line: number; ch: number } +} +export type LinterDefinition = (text: string) => Promise diff --git a/helpers/editor/utils.ts b/helpers/editor/utils.ts new file mode 100644 index 00000000..18b31176 --- /dev/null +++ b/helpers/editor/utils.ts @@ -0,0 +1,23 @@ +export function convertIndexToLineCh( + text: string, + i: number +): { line: number; ch: number } { + const lines = text.split("/n") + + let line = 0 + let counter = 0 + + while (line < lines.length) { + if (i > lines[line].length + counter) { + counter += lines[line].length + 1 + line++ + } else { + return { + line: line + 1, + ch: i - counter + 1, + } + } + } + + throw new Error("Invalid input") +} From 3addfe8d4b49a79f1593f2d94e0e3ec2959a165f Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 1 Sep 2021 16:45:49 +0530 Subject: [PATCH 07/65] feat: linter for prerequest and testscripts --- helpers/editor/linting/preRequest.ts | 69 ++++++++++++++++++++++++++++ helpers/editor/linting/testScript.ts | 69 ++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 helpers/editor/linting/preRequest.ts create mode 100644 helpers/editor/linting/testScript.ts diff --git a/helpers/editor/linting/preRequest.ts b/helpers/editor/linting/preRequest.ts new file mode 100644 index 00000000..db42d986 --- /dev/null +++ b/helpers/editor/linting/preRequest.ts @@ -0,0 +1,69 @@ +import * as esprima from "esprima" +import { LinterDefinition, LinterResult } from "./linter" +import { performPreRequestLinting } from "~/helpers/tern" + +const linter: LinterDefinition = async (text) => { + let results: LinterResult[] = [] + + // Semantic linting + const semanticLints = await performPreRequestLinting(text) + + results = results.concat( + semanticLints.map((lint: any) => ({ + from: lint.from, + to: lint.to, + severity: "error", + message: `[semantic] ${lint.message}`, + })) + ) + + // Syntax linting + try { + const res: any = esprima.parseScript(text, { tolerant: true }) + if (res.errors && res.errors.length > 0) { + results = results.concat( + res.errors.map((err: any) => { + const fromPos: { line: number; ch: number } = { + line: err.lineNumber - 1, + ch: err.column - 1, + } + + const toPos: { line: number; ch: number } = { + line: err.lineNumber - 1, + ch: err.column, + } + + return { + from: fromPos, + to: toPos, + message: `[syntax] ${err.description}`, + severity: "error", + } + }) + ) + } + } catch (e) { + const fromPos: { line: number; ch: number } = { + line: e.lineNumber - 1, + ch: e.column - 1, + } + + const toPos: { line: number; ch: number } = { + line: e.lineNumber - 1, + ch: e.column, + } + + results = results.concat([ + { + from: fromPos, + to: toPos, + message: `[syntax] ${e.description}`, + severity: "error", + }, + ]) + } + + return results +} + +export default linter diff --git a/helpers/editor/linting/testScript.ts b/helpers/editor/linting/testScript.ts new file mode 100644 index 00000000..902d1778 --- /dev/null +++ b/helpers/editor/linting/testScript.ts @@ -0,0 +1,69 @@ +import * as esprima from "esprima" +import { LinterDefinition, LinterResult } from "./linter" +import { performTestLinting } from "~/helpers/tern" + +const linter: LinterDefinition = async (text) => { + let results: LinterResult[] = [] + + // Semantic linting + const semanticLints = await performTestLinting(text) + + results = results.concat( + semanticLints.map((lint: any) => ({ + from: lint.from, + to: lint.to, + severity: "error", + message: `[semantic] ${lint.message}`, + })) + ) + + // Syntax linting + try { + const res: any = esprima.parseScript(text, { tolerant: true }) + if (res.errors && res.errors.length > 0) { + results = results.concat( + res.errors.map((err: any) => { + const fromPos: { line: number; ch: number } = { + line: err.lineNumber - 1, + ch: err.column - 1, + } + + const toPos: { line: number; ch: number } = { + line: err.lineNumber - 1, + ch: err.column, + } + + return { + from: fromPos, + to: toPos, + message: `[syntax] ${err.description}`, + severity: "error", + } + }) + ) + } + } catch (e) { + const fromPos: { line: number; ch: number } = { + line: e.lineNumber - 1, + ch: e.column - 1, + } + + const toPos: { line: number; ch: number } = { + line: e.lineNumber - 1, + ch: e.column, + } + + results = results.concat([ + { + from: fromPos, + to: toPos, + message: `[syntax] ${e.description}`, + severity: "error", + }, + ]) + } + + return results +} + +export default linter From c938abf606a25511804c0d2bb6b1de12529fceba Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Wed, 1 Sep 2021 17:33:54 +0530 Subject: [PATCH 08/65] feat: placeholder, auto-close brackets, search, line wrap --- components/smart/CodeMirror.vue | 2 ++ helpers/editor/codemirror.ts | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 3cf60203..7298927e 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -39,6 +39,8 @@ const editor = ref(null) useCodemirror(editor, value, { extendedEditorConfig: { mode: props.mode, + placeholder: props.placeholder, + lineWrapping: props.wrap, }, linter: props.linter, }) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 49969267..c9bc7e1d 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -4,6 +4,7 @@ import "codemirror/theme/juejin.css" import "codemirror/lib/codemirror.css" import "codemirror/addon/lint/lint.css" +import "codemirror/addon/dialog/dialog.css" import "codemirror/addon/fold/foldgutter.css" import "codemirror/addon/fold/foldgutter" @@ -12,6 +13,12 @@ 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/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 { watch, onMounted, ref, Ref } from "@nuxtjs/composition-api" import { LinterDefinition } from "./linting/linter" @@ -28,6 +35,7 @@ const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { autoRefresh: true, lineNumbers: true, foldGutter: true, + autoCloseBrackets: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], } From 8430921e4e84e7c95fe71a29a82b964b0c6b52e6 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 1 Sep 2021 20:32:33 +0530 Subject: [PATCH 09/65] feat: codemirror editor options are reactive --- helpers/editor/codemirror.ts | 42 ++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index c9bc7e1d..99d665fd 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -26,7 +26,7 @@ import { LinterDefinition } from "./linting/linter" const DEFAULT_THEME = "juejin" type CodeMirrorOptions = { - extendedEditorConfig: CodeMirror.EditorConfiguration + extendedEditorConfig: Omit linter: LinterDefinition | null } @@ -55,17 +55,34 @@ export function useCodemirror( ) { const cm = ref(null) + 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 updateLinterConfig = () => { + if (options.linter) { + cm.value?.setOption("lint", options.linter) + } + } + // Boot-up CodeMirror, set the value and listeners onMounted(() => { - cm.value = CodeMirror(el.value!, { - ...DEFAULT_EDITOR_CONFIG, - ...options.extendedEditorConfig, - }) - cm.value.setValue(value.value) + cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG) - if (options.linter) { - cm.value.setOption("lint", options.linter) - } + updateEditorConfig() + updateLinterConfig() cm.value.on("change", (instance) => { // External update propagation (via watchers) should be ignored @@ -75,6 +92,13 @@ export function useCodemirror( }) }) + // If the editor properties are reactive, watch for updates + watch(() => options.extendedEditorConfig, updateEditorConfig, { + immediate: true, + deep: true, + }) + watch(() => options.linter, updateLinterConfig, { immediate: true }) + // Watch value updates watch(value, (newVal) => { // Check if we are mounted From 0c2cec46a7289b445f65cdec013bf9a31b43b94c Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Mon, 6 Sep 2021 23:30:43 +0530 Subject: [PATCH 10/65] feat: implement gql query linting in codemirror --- helpers/editor/linting/gqlQuery.ts | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 helpers/editor/linting/gqlQuery.ts diff --git a/helpers/editor/linting/gqlQuery.ts b/helpers/editor/linting/gqlQuery.ts new file mode 100644 index 00000000..648cfa73 --- /dev/null +++ b/helpers/editor/linting/gqlQuery.ts @@ -0,0 +1,58 @@ +import { Ref } from "@nuxtjs/composition-api" +import { + GraphQLError, + GraphQLSchema, + parse as gqlParse, + validate as gqlValidate, +} from "graphql" +import { LinterDefinition, LinterResult } from "./linter" + +/** + * Creates a Linter function that can lint a GQL query against a given + * schema + */ +export const createGQLQueryLinter: ( + schema: Ref +) => LinterDefinition = (schema: Ref) => (text) => { + if (text === "") return Promise.resolve([]) + if (!schema.value) return Promise.resolve([]) + + try { + const doc = gqlParse(text) + + const results = gqlValidate(schema.value, doc).map( + ({ locations, message }) => + { + from: { + line: locations![0].line - 1, + ch: locations![0].column - 1, + }, + to: { + line: locations![0].line - 1, + ch: locations![0].column, + }, + message, + severity: "error", + } + ) + + return Promise.resolve(results) + } catch (e) { + const err = e as GraphQLError + + return Promise.resolve([ + { + from: { + line: err.locations![0].line - 1, + ch: err.locations![0].column - 1, + }, + to: { + line: err.locations![0].line - 1, + ch: err.locations![0].column, + }, + message: err.message, + severity: "error", + }, + ]) + } +} From 12cd7940c6c8decd9b4e5f256bd636bc56218856 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Mon, 6 Sep 2021 23:47:26 +0530 Subject: [PATCH 11/65] feat: json linter support for codemirror --- helpers/editor/linting/json.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 helpers/editor/linting/json.ts diff --git a/helpers/editor/linting/json.ts b/helpers/editor/linting/json.ts new file mode 100644 index 00000000..46a69019 --- /dev/null +++ b/helpers/editor/linting/json.ts @@ -0,0 +1,21 @@ +import { convertIndexToLineCh } from "../utils" +import { LinterDefinition, LinterResult } from "./linter" +import jsonParse from "~/helpers/jsonParse" + +const linter: LinterDefinition = (text) => { + try { + jsonParse(text) + return Promise.resolve([]) + } catch (e: any) { + return Promise.resolve([ + { + from: convertIndexToLineCh(text, e.start), + to: convertIndexToLineCh(text, e.end), + message: e.message, + severity: "error", + }, + ]) + } +} + +export default linter From 8a5fd4f745b3acf7eb35284c049b6ae728d1e8ce Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 7 Sep 2021 12:14:13 +0530 Subject: [PATCH 12/65] feat: reactive codemirror theme --- assets/scss/styles.scss | 26 +++++++++++++++++++++----- components/smart/CodeMirror.vue | 18 ++---------------- helpers/editor/codemirror.ts | 32 +++++++++++++++++++++++++++----- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss index f54c9535..cfc1a619 100644 --- a/assets/scss/styles.scss +++ b/assets/scss/styles.scss @@ -17,7 +17,7 @@ ::-webkit-scrollbar-thumb { @apply bg-divider bg-clip-content; @apply rounded-full; - @apply border-solid border-4 border-transparent; + @apply border-solid border-transparent border-4; @apply hover:(bg-dividerDark bg-clip-content); } @@ -116,8 +116,8 @@ a { &.link { @apply items-center; - @apply px-1 py-0.5; - @apply -mx-1 -my-0.5; + @apply py-0.5 px-1; + @apply -my-0.5 -mx-1; @apply text-accent; @apply rounded; @apply hover:text-accentDark; @@ -198,7 +198,7 @@ hr { .textarea { @apply flex; @apply w-full; - @apply px-4 py-2; + @apply py-2 px-4; @apply bg-transparent; @apply rounded; @apply text-secondaryDark; @@ -293,7 +293,7 @@ input[type="checkbox"] { @apply cursor-pointer; &::before { - @apply border-2 border-divider; + @apply border-divider border-2; @apply rounded; @apply inline-flex; @apply items-center; @@ -461,6 +461,22 @@ input[type="checkbox"] { @apply w-full; } +.CodeMirror { + @apply block; + @apply border-b; + @apply border-dividerLight; + @apply w-full; + @apply h-auto; +} + +.CodeMirror * { + font-family: "Roboto Mono", monospace; +} + +.CodeMirror-scroll { + @apply min-h-32; +} + @media (max-width: 767px) { main { margin-bottom: env(safe-area-inset-bottom); diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 7298927e..2280af84 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -13,12 +13,13 @@ const props = withDefaults( defineProps<{ value: string mode: string - placeholder: string + placeholder?: string wrap: boolean linter: LinterDefinition | null }>(), { linter: null as any, + placeholder: "", } ) @@ -45,18 +46,3 @@ useCodemirror(editor, value, { linter: props.linter, }) - - diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 99d665fd..fc7c1f97 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -1,6 +1,8 @@ import CodeMirror from "codemirror" -import "codemirror/theme/juejin.css" +import "codemirror/theme/base16-light.css" +import "codemirror/theme/base16-dark.css" +import "codemirror/theme/3024-night.css" import "codemirror/lib/codemirror.css" import "codemirror/addon/lint/lint.css" @@ -20,18 +22,15 @@ import "codemirror/addon/search/searchcursor" import "codemirror/addon/search/jump-to-line" import "codemirror/addon/dialog/dialog" -import { watch, onMounted, ref, Ref } from "@nuxtjs/composition-api" +import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api" import { LinterDefinition } from "./linting/linter" -const DEFAULT_THEME = "juejin" - type CodeMirrorOptions = { extendedEditorConfig: Omit linter: LinterDefinition | null } const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { - theme: DEFAULT_THEME, autoRefresh: true, lineNumbers: true, foldGutter: true, @@ -81,6 +80,7 @@ export function useCodemirror( onMounted(() => { cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG) + setTheme() updateEditorConfig() updateLinterConfig() @@ -92,6 +92,28 @@ export function useCodemirror( }) }) + const setTheme = () => { + const { $colorMode } = useContext() as any + if (cm.value) { + cm.value?.setOption("theme", getThemeName($colorMode.value)) + } + } + + const getThemeName = (mode: string) => { + switch (mode) { + case "system": + return "default" + case "light": + return "base16-light" + case "dark": + return "base16-dark" + case "black": + return "3024-night" + default: + return "default" + } + } + // If the editor properties are reactive, watch for updates watch(() => options.extendedEditorConfig, updateEditorConfig, { immediate: true, From a5197ee5449461f07284ac809c861f1c8e97fe06 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 7 Sep 2021 16:26:26 +0530 Subject: [PATCH 13/65] refactor: github flavored codemirror light theme --- components/smart/CodeMirror.vue | 5 +++-- helpers/editor/codemirror.ts | 4 ++-- package-lock.json | 11 +++++++++++ package.json | 1 + 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 2280af84..58d8997f 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -14,12 +14,13 @@ const props = withDefaults( value: string mode: string placeholder?: string - wrap: boolean + wrap?: boolean linter: LinterDefinition | null }>(), { - linter: null as any, placeholder: "", + wrap: true, + linter: null as any, } ) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index fc7c1f97..6681f792 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -1,6 +1,6 @@ import CodeMirror from "codemirror" -import "codemirror/theme/base16-light.css" +import "codemirror-github-light/lib/codemirror-github-light-theme.css" import "codemirror/theme/base16-dark.css" import "codemirror/theme/3024-night.css" @@ -104,7 +104,7 @@ export function useCodemirror( case "system": return "default" case "light": - return "base16-light" + return "github-light" case "dark": return "base16-dark" case "black": diff --git a/package-lock.json b/package-lock.json index a456b63a..bff97367 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "codemirror": "^5.62.3", + "codemirror-github-light": "^0.4.2", "core-js": "^3.16.4", "esprima": "^4.0.1", "firebase": "^9.0.0", @@ -13107,6 +13108,11 @@ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" }, + "node_modules/codemirror-github-light": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/codemirror-github-light/-/codemirror-github-light-0.4.2.tgz", + "integrity": "sha1-iUl8JJWEipRaLNIaqj5Wa4d7FDU=" + }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -46127,6 +46133,11 @@ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" }, + "codemirror-github-light": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/codemirror-github-light/-/codemirror-github-light-0.4.2.tgz", + "integrity": "sha1-iUl8JJWEipRaLNIaqj5Wa4d7FDU=" + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", diff --git a/package.json b/package.json index 45a29101..1e684e93 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "codemirror": "^5.62.3", + "codemirror-github-light": "^0.4.2", "core-js": "^3.16.4", "esprima": "^4.0.1", "firebase": "^9.0.0", From d4d3d96bbb669b2a02454be8d311cc06b4a066cc Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Tue, 7 Sep 2021 22:12:38 +0530 Subject: [PATCH 14/65] feat: implement base autocomplete implementation for codemirror along with preRequest autocompletion --- components/smart/CodeMirror.vue | 4 +++ helpers/editor/codemirror.ts | 38 +++++++++++++++++++++++++ helpers/editor/completion/index.ts | 33 +++++++++++++++++++++ helpers/editor/completion/preRequest.ts | 30 +++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 helpers/editor/completion/index.ts create mode 100644 helpers/editor/completion/preRequest.ts diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 58d8997f..abe3845d 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -8,6 +8,7 @@ import "codemirror/mode/javascript/javascript" import { ref, watch } from "@nuxtjs/composition-api" import { useCodemirror } from "~/helpers/editor/codemirror" import { LinterDefinition } from "~/helpers/editor/linting/linter" +import { Completer } from "~/helpers/editor/completion" const props = withDefaults( defineProps<{ @@ -16,11 +17,13 @@ const props = withDefaults( placeholder?: string wrap?: boolean linter: LinterDefinition | null + completer: Completer | null }>(), { placeholder: "", wrap: true, linter: null as any, + completer: null as any, } ) @@ -45,5 +48,6 @@ useCodemirror(editor, value, { lineWrapping: props.wrap, }, linter: props.linter, + completer: props.completer, }) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 6681f792..852d4659 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -7,6 +7,7 @@ import "codemirror/theme/3024-night.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" @@ -15,6 +16,7 @@ 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" @@ -24,10 +26,12 @@ import "codemirror/addon/dialog/dialog" import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api" import { LinterDefinition } from "./linting/linter" +import { Completer } from "./completion" type CodeMirrorOptions = { extendedEditorConfig: Omit linter: LinterDefinition | null + completer: Completer | null } const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { @@ -76,6 +80,31 @@ export function useCodemirror( } } + 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 result = await options.completer!(text, pos) + + console.log("complete!") + console.log(result) + + return { + from: result.start, + to: result.end, + list: result.completions + .sort((a, b) => a.score - b.score) + .map((x) => x.text), + } + }, + }) + } + } + // Boot-up CodeMirror, set the value and listeners onMounted(() => { cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG) @@ -83,6 +112,7 @@ export function useCodemirror( setTheme() updateEditorConfig() updateLinterConfig() + updateCompleterConfig() cm.value.on("change", (instance) => { // External update propagation (via watchers) should be ignored @@ -90,6 +120,13 @@ export function useCodemirror( value.value = instance.getValue() } }) + + /* TODO: Show autocomplete on typing (this is just for testing) */ + cm.value.on("keyup", (instance, event) => { + if (!instance.state.completionActive && event.key !== "Enter") { + instance.showHint() + } + }) }) const setTheme = () => { @@ -120,6 +157,7 @@ export function useCodemirror( deep: true, }) watch(() => options.linter, updateLinterConfig, { immediate: true }) + watch(() => options.completer, updateCompleterConfig, { immediate: true }) // Watch value updates watch(value, (newVal) => { diff --git a/helpers/editor/completion/index.ts b/helpers/editor/completion/index.ts new file mode 100644 index 00000000..f5f927b0 --- /dev/null +++ b/helpers/editor/completion/index.ts @@ -0,0 +1,33 @@ +export type CompletionEntry = { + text: string + meta: string + score: number +} + +export type CompleterResult = { + /** + * List of completions to display + */ + completions: CompletionEntry[] + /** + * Start of the completion position + * (on completion the start..end region is replaced) + */ + start: { line: number; ch: number } + /** + * End of the completion position + * (on completion the start..end region is replaced) + */ + end: { line: number; ch: number } +} + +export type Completer = ( + /** + * The contents of the editor + */ + text: string, + /** + * Position where the completer is fired + */ + completePos: { line: number; ch: number } +) => Promise diff --git a/helpers/editor/completion/preRequest.ts b/helpers/editor/completion/preRequest.ts new file mode 100644 index 00000000..1a3387cf --- /dev/null +++ b/helpers/editor/completion/preRequest.ts @@ -0,0 +1,30 @@ +import { convertIndexToLineCh } from "../utils" +import { Completer, CompletionEntry } from "." +import { getPreRequestScriptCompletions } from "~/helpers/tern" + +const completer: Completer = async (text, completePos) => { + const results = await getPreRequestScriptCompletions( + text, + completePos.line, + completePos.ch + ) + + const start = convertIndexToLineCh(text, results.start) + const end = convertIndexToLineCh(text, results.end) + + const completions = results.completions.map((completion: any, i: number) => { + return { + text: completion.name, + meta: completion.isKeyword ? "keyword" : completion.type, + score: results.completions.length - i, + } + }) + + return { + start, + end, + completions, + } +} + +export default completer From f64ff58dbc007ab806aa40f7e044dab608046ecb Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 04:58:23 +0530 Subject: [PATCH 15/65] feat: test script auto completion for codemirror --- helpers/editor/completion/testScript.ts | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 helpers/editor/completion/testScript.ts diff --git a/helpers/editor/completion/testScript.ts b/helpers/editor/completion/testScript.ts new file mode 100644 index 00000000..94ad4d53 --- /dev/null +++ b/helpers/editor/completion/testScript.ts @@ -0,0 +1,30 @@ +import { convertIndexToLineCh } from "../utils" +import { Completer, CompletionEntry } from "." +import { getTestScriptCompletions } from "~/helpers/tern" + +export const completer: Completer = async (text, completePos) => { + const results = await getTestScriptCompletions( + text, + completePos.line, + completePos.ch + ) + + const start = convertIndexToLineCh(text, results.start) + const end = convertIndexToLineCh(text, results.end) + + const completions = results.completions.map((completion: any, i: number) => { + return { + text: completion.name, + meta: completion.isKeyword ? "keyword" : completion.type, + score: results.completions.length - i, + } + }) + + return { + start, + end, + completions, + } +} + +export default completer From b016d3fd9d8220969db67fcf3165411e90e59e46 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 05:36:46 +0530 Subject: [PATCH 16/65] refactor: pass current token position to auto completers on codemirror --- helpers/editor/codemirror.ts | 11 ++++++++--- helpers/editor/completion/index.ts | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 852d4659..3e9fe5d3 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -27,6 +27,7 @@ import "codemirror/addon/dialog/dialog" import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api" import { LinterDefinition } from "./linting/linter" import { Completer } from "./completion" +import { convertIndexToLineCh } from "./utils" type CodeMirrorOptions = { extendedEditorConfig: Omit @@ -88,10 +89,14 @@ export function useCodemirror( const pos = editor.getCursor() const text = editor.getValue() - const result = await options.completer!(text, pos) + const token = editor.getTokenAt(pos) - console.log("complete!") - console.log(result) + const result = await options.completer!(text, pos, { + start: convertIndexToLineCh(text, token.start), + end: convertIndexToLineCh(text, token.end), + }) + + if (!result) return null return { from: result.start, diff --git a/helpers/editor/completion/index.ts b/helpers/editor/completion/index.ts index f5f927b0..d2341444 100644 --- a/helpers/editor/completion/index.ts +++ b/helpers/editor/completion/index.ts @@ -29,5 +29,9 @@ export type Completer = ( /** * Position where the completer is fired */ - completePos: { line: number; ch: number } -) => Promise + completePos: { line: number; ch: number }, + completeTokenLocation: { + start: { line: number; ch: number } + end: { line: number; ch: number } + } +) => Promise From 162b3d61924e5a8bdfe66ce1edd6d56a16034ba9 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 05:38:03 +0530 Subject: [PATCH 17/65] feat: gql query autocompletion on codemirror --- helpers/editor/completion/gqlQuery.ts | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 helpers/editor/completion/gqlQuery.ts diff --git a/helpers/editor/completion/gqlQuery.ts b/helpers/editor/completion/gqlQuery.ts new file mode 100644 index 00000000..3ff56d13 --- /dev/null +++ b/helpers/editor/completion/gqlQuery.ts @@ -0,0 +1,29 @@ +import { Ref } from "@nuxtjs/composition-api" +import { GraphQLSchema } from "graphql" +import { getAutocompleteSuggestions } from "graphql-language-service-interface" +import { Completer, CompleterResult, CompletionEntry } from "." + +const completer: (schemaRef: Ref) => Completer = + (schemaRef: Ref) => (text, completePos, tokenPos) => { + if (!schemaRef.value) return Promise.resolve(null) + + const completions = getAutocompleteSuggestions(schemaRef.value, text, { + line: completePos.line, + character: completePos.ch, + } as any) + + return Promise.resolve({ + start: tokenPos.start, + end: tokenPos.end, + completions: completions.map( + (x, i) => + { + text: x.label!, + meta: x.detail!, + score: completions.length - i, + } + ), + }) + } + +export default completer From 28aeac45337f1c2a231a58ed1b5ef071cb1d1fec Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 06:00:23 +0530 Subject: [PATCH 18/65] fix: codemirror theme not changing when color mode is updated --- helpers/editor/codemirror.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 3e9fe5d3..bd5e8a99 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -57,6 +57,8 @@ export function useCodemirror( value: Ref, options: CodeMirrorOptions ) { + const { $colorMode } = useContext() as any + const cm = ref(null) const updateEditorConfig = () => { @@ -135,7 +137,6 @@ export function useCodemirror( }) const setTheme = () => { - const { $colorMode } = useContext() as any if (cm.value) { cm.value?.setOption("theme", getThemeName($colorMode.value)) } @@ -175,6 +176,9 @@ export function useCodemirror( } }) + // Watch color mode updates and update theme + watch(() => $colorMode.value, setTheme) + return { cm, } From 26c8f356883f9f945c2388c5aeadd05d5099f23b Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 19:51:43 +0530 Subject: [PATCH 19/65] refactor: map ctrl-space to autocomplete by default in codemirror --- helpers/editor/codemirror.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index bd5e8a99..e1e8791a 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -41,6 +41,9 @@ const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { foldGutter: true, autoCloseBrackets: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], + extraKeys: { + "Ctrl-Space": "autocomplete", + } } /** @@ -128,12 +131,12 @@ export function useCodemirror( } }) - /* TODO: Show autocomplete on typing (this is just for testing) */ - cm.value.on("keyup", (instance, event) => { - if (!instance.state.completionActive && event.key !== "Enter") { - instance.showHint() - } - }) + // /* TODO: Show autocomplete on typing (this is just for testing) */ + // cm.value.on("keyup", (instance, event) => { + // if (!instance.state.completionActive && event.key !== "Enter") { + // instance.showHint() + // } + // }) }) const setTheme = () => { From 66c489da8f48e8b172f54145f4d64b1f122bdd55 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Wed, 8 Sep 2021 20:27:36 +0530 Subject: [PATCH 20/65] fix: broken conditional rendering of codemirror Co-authored-by: Andrew Bastin --- helpers/editor/codemirror.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index e1e8791a..487365c4 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -43,7 +43,7 @@ const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], extraKeys: { "Ctrl-Space": "autocomplete", - } + }, } /** @@ -115,8 +115,9 @@ export function useCodemirror( } } - // Boot-up CodeMirror, set the value and listeners - onMounted(() => { + const initialize = () => { + if (!el.value) return + cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG) setTheme() @@ -130,15 +131,16 @@ export function useCodemirror( value.value = instance.getValue() } }) + } - // /* TODO: Show autocomplete on typing (this is just for testing) */ - // cm.value.on("keyup", (instance, event) => { - // if (!instance.state.completionActive && event.key !== "Enter") { - // instance.showHint() - // } - // }) + // Boot-up CodeMirror, set the value and listeners + onMounted(() => { + initialize() }) + // Reinitialize if the target ref updates + watch(el, initialize) + const setTheme = () => { if (cm.value) { cm.value?.setOption("theme", getThemeName($colorMode.value)) From 8de544696db7ebb6cb8ca6ab6daa4a410e0fac21 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 31 Aug 2021 00:03:07 +0530 Subject: [PATCH 21/65] feat: init codemirror --- components/smart/CodeMirror.vue | 85 +++++++++++++++++++++++++++++++++ package-lock.json | 11 +++++ package.json | 1 + 3 files changed, 97 insertions(+) create mode 100644 components/smart/CodeMirror.vue diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue new file mode 100644 index 00000000..f4dfc315 --- /dev/null +++ b/components/smart/CodeMirror.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/package-lock.json b/package-lock.json index 098126f2..83a929fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "ace-builds": "^1.4.12", "acorn": "^8.5.0", "acorn-walk": "^8.2.0", + "codemirror": "^5.62.3", "core-js": "^3.17.2", "esprima": "^4.0.1", "firebase": "^9.0.1", @@ -13074,6 +13075,11 @@ "node": ">=0.10.0" } }, + "node_modules/codemirror": { + "version": "5.62.3", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", + "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" + }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -46082,6 +46088,11 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "codemirror": { + "version": "5.62.3", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", + "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", diff --git a/package.json b/package.json index 94374cc7..8206b6b8 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "ace-builds": "^1.4.12", "acorn": "^8.5.0", "acorn-walk": "^8.2.0", + "codemirror": "^5.62.3", "core-js": "^3.17.2", "esprima": "^4.0.1", "firebase": "^9.0.1", From 6a1d201e0ede760427d267ef2295332fa247db93 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 31 Aug 2021 10:47:00 +0530 Subject: [PATCH 22/65] refactor: ts codemirror --- components/smart/CodeMirror.vue | 108 +++++++++++++++----------------- package-lock.json | 49 +++++++++++++++ package.json | 1 + 3 files changed, 102 insertions(+), 56 deletions(-) diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index f4dfc315..f29fe92a 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -2,84 +2,80 @@
- - diff --git a/package-lock.json b/package-lock.json index 83a929fb..b79dee05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "@nuxtjs/stylelint-module": "^4.0.0", "@nuxtjs/svg": "^0.2.0", "@testing-library/jest-dom": "^5.14.1", + "@types/codemirror": "^5.60.2", "@types/cookie": "^0.4.1", "@types/lodash": "^4.14.172", "@types/splitpanes": "^2.2.1", @@ -7892,6 +7893,15 @@ "node": ">=0.10.0" } }, + "node_modules/@types/codemirror": { + "version": "5.60.2", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.2.tgz", + "integrity": "sha512-tk8YxckrdU49GaJYRKxdzzzXrTlyT2nQGnobb8rAk34jt+kYXOxPKGqNgr7SJpl5r6YGaRD4CDfqiL+6A+/z7w==", + "dev": true, + "dependencies": { + "@types/tern": "*" + } + }, "node_modules/@types/component-emitter": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", @@ -7941,6 +7951,12 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" }, + "node_modules/@types/estree": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "dev": true + }, "node_modules/@types/etag": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@types/etag/-/etag-1.8.0.tgz", @@ -8325,6 +8341,15 @@ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==" }, + "node_modules/@types/tern": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", + "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/terser-webpack-plugin": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@types/terser-webpack-plugin/-/terser-webpack-plugin-4.2.1.tgz", @@ -41849,6 +41874,15 @@ } } }, + "@types/codemirror": { + "version": "5.60.2", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.2.tgz", + "integrity": "sha512-tk8YxckrdU49GaJYRKxdzzzXrTlyT2nQGnobb8rAk34jt+kYXOxPKGqNgr7SJpl5r6YGaRD4CDfqiL+6A+/z7w==", + "dev": true, + "requires": { + "@types/tern": "*" + } + }, "@types/component-emitter": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", @@ -41898,6 +41932,12 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" }, + "@types/estree": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "dev": true + }, "@types/etag": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@types/etag/-/etag-1.8.0.tgz", @@ -42282,6 +42322,15 @@ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==" }, + "@types/tern": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", + "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, "@types/terser-webpack-plugin": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@types/terser-webpack-plugin/-/terser-webpack-plugin-4.2.1.tgz", diff --git a/package.json b/package.json index 8206b6b8..2420d753 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@nuxtjs/stylelint-module": "^4.0.0", "@nuxtjs/svg": "^0.2.0", "@testing-library/jest-dom": "^5.14.1", + "@types/codemirror": "^5.60.2", "@types/cookie": "^0.4.1", "@types/lodash": "^4.14.172", "@types/splitpanes": "^2.2.1", From 2bafae5397edf47c847b7a1f4fb886482109b35a Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 31 Aug 2021 16:10:35 +0530 Subject: [PATCH 23/65] feat: line wrap, auto close brackets, placeholder on codemirror --- components/smart/CodeMirror.vue | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index f29fe92a..292732f1 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -9,14 +9,10 @@ import "codemirror/lib/codemirror.css" import "codemirror/theme/juejin.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/mode/javascript/javascript" +import "codemirror/addon/selection/active-line" +import "codemirror/addon/edit/closebrackets" +import "codemirror/addon/display/placeholder" import { onMounted, ref, watch } from "@nuxtjs/composition-api" @@ -25,6 +21,8 @@ const DEFAULT_THEME = "juejin" const props = defineProps<{ value: string mode: string + placeholder: string + wrap: boolean }>() const emit = defineEmits<{ @@ -53,14 +51,17 @@ onMounted(() => { cm.value = Codemirror(editor.value, { value: props.value, mode: props.mode, + lineWrapping: props.wrap, + placeholder: props.placeholder, autoRefresh: true, lineNumbers: true, - foldGutter: true, + styleActiveLine: true, + autoCloseBrackets: true, theme: DEFAULT_THEME, - gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], + gutters: ["CodeMirror-linenumbers"], }) - cm.value?.on("change", (instance) => { - const val = instance.getValue() + cm.value?.on("change", (cm) => { + const val = cm.getValue() emit("input", val) }) }) @@ -73,6 +74,7 @@ onMounted(() => { @apply border-dividerLight; @apply w-full; @apply h-auto; + @apply font-mono; } .CodeMirror-scroll { From c81178ae266c11cddbe4814cf3f8cc99fd3836b4 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Tue, 31 Aug 2021 22:20:42 +0530 Subject: [PATCH 24/65] refactor: extract common codemirror logic out to composable --- components/smart/CodeMirror.vue | 49 ++++------------------- helpers/editor/codemirror.ts | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 helpers/editor/codemirror.ts diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 292732f1..6068778c 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -3,20 +3,10 @@ diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts new file mode 100644 index 00000000..9a282979 --- /dev/null +++ b/helpers/editor/codemirror.ts @@ -0,0 +1,69 @@ +import CodeMirror from "codemirror" + +import "codemirror/theme/juejin.css" + +import "codemirror/lib/codemirror.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 { watch, onMounted, ref, Ref } from "@nuxtjs/composition-api" + +const DEFAULT_THEME = "juejin" + +const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { + theme: DEFAULT_THEME, + autoRefresh: true, + lineNumbers: true, + foldGutter: true, + gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], +} + +/** + * 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, + value: Ref, + options: CodeMirror.EditorConfiguration +) { + const cm = ref(null) + + // Boot-up CodeMirror, set the value and listeners + onMounted(() => { + cm.value = CodeMirror(el.value!, { ...DEFAULT_EDITOR_CONFIG, ...options }) + cm.value.setValue(value.value) + + cm.value.on("change", (instance) => { + // External update propagation (via watchers) should be ignored + if (instance.getValue() !== value.value) { + value.value = instance.getValue() + } + }) + }) + + // 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) + } + } + }) + + return { + cm, + } +} From 10a11d6725a345c3c1c13300de0cb4314462d67a Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 1 Sep 2021 16:34:02 +0530 Subject: [PATCH 25/65] refactor: add types for esprima --- package-lock.json | 19 +++++++++++++++++++ package.json | 1 + 2 files changed, 20 insertions(+) diff --git a/package-lock.json b/package-lock.json index b79dee05..fa31b90e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "@testing-library/jest-dom": "^5.14.1", "@types/codemirror": "^5.60.2", "@types/cookie": "^0.4.1", + "@types/esprima": "^4.0.3", "@types/lodash": "^4.14.172", "@types/splitpanes": "^2.2.1", "@vue/runtime-dom": "^3.2.10", @@ -7951,6 +7952,15 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" }, + "node_modules/@types/esprima": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/esprima/-/esprima-4.0.3.tgz", + "integrity": "sha512-jo14dIWVVtF0iMsKkYek6++4cWJjwpvog+rchLulwgFJGTXqIeTdCOvY0B3yMLTaIwMcKCdJ6mQbSR6wYHy98A==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", @@ -41932,6 +41942,15 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" }, + "@types/esprima": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/esprima/-/esprima-4.0.3.tgz", + "integrity": "sha512-jo14dIWVVtF0iMsKkYek6++4cWJjwpvog+rchLulwgFJGTXqIeTdCOvY0B3yMLTaIwMcKCdJ6mQbSR6wYHy98A==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, "@types/estree": { "version": "0.0.50", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", diff --git a/package.json b/package.json index 2420d753..b121edec 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "@testing-library/jest-dom": "^5.14.1", "@types/codemirror": "^5.60.2", "@types/cookie": "^0.4.1", + "@types/esprima": "^4.0.3", "@types/lodash": "^4.14.172", "@types/splitpanes": "^2.2.1", "@vue/runtime-dom": "^3.2.10", From 071761a61e7b15f3d22e71766e2d9f29b14f8296 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 1 Sep 2021 16:41:14 +0530 Subject: [PATCH 26/65] feat: codemirror linting system --- components/smart/CodeMirror.vue | 24 +++++++++++++++++------- helpers/editor/codemirror.ts | 19 +++++++++++++++++-- helpers/editor/linting/linter.ts | 7 +++++++ helpers/editor/utils.ts | 23 +++++++++++++++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 helpers/editor/linting/linter.ts create mode 100644 helpers/editor/utils.ts diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 6068778c..3cf60203 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -7,13 +7,20 @@ import "codemirror/mode/javascript/javascript" import { ref, watch } from "@nuxtjs/composition-api" import { useCodemirror } from "~/helpers/editor/codemirror" +import { LinterDefinition } from "~/helpers/editor/linting/linter" -const props = defineProps<{ - value: string - mode: string - placeholder: string - wrap: boolean -}>() +const props = withDefaults( + defineProps<{ + value: string + mode: string + placeholder: string + wrap: boolean + linter: LinterDefinition | null + }>(), + { + linter: null as any, + } +) const emit = defineEmits<{ (e: "input", value: string): void @@ -30,7 +37,10 @@ watch(value, (val) => emit("input", val)) const editor = ref(null) useCodemirror(editor, value, { - mode: props.mode, + extendedEditorConfig: { + mode: props.mode, + }, + linter: props.linter, }) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 9a282979..49969267 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -3,6 +3,7 @@ import CodeMirror from "codemirror" import "codemirror/theme/juejin.css" import "codemirror/lib/codemirror.css" +import "codemirror/addon/lint/lint.css" import "codemirror/addon/fold/foldgutter.css" import "codemirror/addon/fold/foldgutter" @@ -10,11 +11,18 @@ 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 { watch, onMounted, ref, Ref } from "@nuxtjs/composition-api" +import { LinterDefinition } from "./linting/linter" const DEFAULT_THEME = "juejin" +type CodeMirrorOptions = { + extendedEditorConfig: CodeMirror.EditorConfiguration + linter: LinterDefinition | null +} + const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { theme: DEFAULT_THEME, autoRefresh: true, @@ -35,15 +43,22 @@ const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { export function useCodemirror( el: Ref, value: Ref, - options: CodeMirror.EditorConfiguration + options: CodeMirrorOptions ) { const cm = ref(null) // Boot-up CodeMirror, set the value and listeners onMounted(() => { - cm.value = CodeMirror(el.value!, { ...DEFAULT_EDITOR_CONFIG, ...options }) + cm.value = CodeMirror(el.value!, { + ...DEFAULT_EDITOR_CONFIG, + ...options.extendedEditorConfig, + }) cm.value.setValue(value.value) + if (options.linter) { + cm.value.setOption("lint", options.linter) + } + cm.value.on("change", (instance) => { // External update propagation (via watchers) should be ignored if (instance.getValue() !== value.value) { diff --git a/helpers/editor/linting/linter.ts b/helpers/editor/linting/linter.ts new file mode 100644 index 00000000..704270cb --- /dev/null +++ b/helpers/editor/linting/linter.ts @@ -0,0 +1,7 @@ +export type LinterResult = { + message: string + severity: "warning" | "error" + from: { line: number; ch: number } + to: { line: number; ch: number } +} +export type LinterDefinition = (text: string) => Promise diff --git a/helpers/editor/utils.ts b/helpers/editor/utils.ts new file mode 100644 index 00000000..18b31176 --- /dev/null +++ b/helpers/editor/utils.ts @@ -0,0 +1,23 @@ +export function convertIndexToLineCh( + text: string, + i: number +): { line: number; ch: number } { + const lines = text.split("/n") + + let line = 0 + let counter = 0 + + while (line < lines.length) { + if (i > lines[line].length + counter) { + counter += lines[line].length + 1 + line++ + } else { + return { + line: line + 1, + ch: i - counter + 1, + } + } + } + + throw new Error("Invalid input") +} From 4e8484ee7c2eb7363944bab356d0d2867f30920f Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 1 Sep 2021 16:45:49 +0530 Subject: [PATCH 27/65] feat: linter for prerequest and testscripts --- helpers/editor/linting/preRequest.ts | 69 ++++++++++++++++++++++++++++ helpers/editor/linting/testScript.ts | 69 ++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 helpers/editor/linting/preRequest.ts create mode 100644 helpers/editor/linting/testScript.ts diff --git a/helpers/editor/linting/preRequest.ts b/helpers/editor/linting/preRequest.ts new file mode 100644 index 00000000..db42d986 --- /dev/null +++ b/helpers/editor/linting/preRequest.ts @@ -0,0 +1,69 @@ +import * as esprima from "esprima" +import { LinterDefinition, LinterResult } from "./linter" +import { performPreRequestLinting } from "~/helpers/tern" + +const linter: LinterDefinition = async (text) => { + let results: LinterResult[] = [] + + // Semantic linting + const semanticLints = await performPreRequestLinting(text) + + results = results.concat( + semanticLints.map((lint: any) => ({ + from: lint.from, + to: lint.to, + severity: "error", + message: `[semantic] ${lint.message}`, + })) + ) + + // Syntax linting + try { + const res: any = esprima.parseScript(text, { tolerant: true }) + if (res.errors && res.errors.length > 0) { + results = results.concat( + res.errors.map((err: any) => { + const fromPos: { line: number; ch: number } = { + line: err.lineNumber - 1, + ch: err.column - 1, + } + + const toPos: { line: number; ch: number } = { + line: err.lineNumber - 1, + ch: err.column, + } + + return { + from: fromPos, + to: toPos, + message: `[syntax] ${err.description}`, + severity: "error", + } + }) + ) + } + } catch (e) { + const fromPos: { line: number; ch: number } = { + line: e.lineNumber - 1, + ch: e.column - 1, + } + + const toPos: { line: number; ch: number } = { + line: e.lineNumber - 1, + ch: e.column, + } + + results = results.concat([ + { + from: fromPos, + to: toPos, + message: `[syntax] ${e.description}`, + severity: "error", + }, + ]) + } + + return results +} + +export default linter diff --git a/helpers/editor/linting/testScript.ts b/helpers/editor/linting/testScript.ts new file mode 100644 index 00000000..902d1778 --- /dev/null +++ b/helpers/editor/linting/testScript.ts @@ -0,0 +1,69 @@ +import * as esprima from "esprima" +import { LinterDefinition, LinterResult } from "./linter" +import { performTestLinting } from "~/helpers/tern" + +const linter: LinterDefinition = async (text) => { + let results: LinterResult[] = [] + + // Semantic linting + const semanticLints = await performTestLinting(text) + + results = results.concat( + semanticLints.map((lint: any) => ({ + from: lint.from, + to: lint.to, + severity: "error", + message: `[semantic] ${lint.message}`, + })) + ) + + // Syntax linting + try { + const res: any = esprima.parseScript(text, { tolerant: true }) + if (res.errors && res.errors.length > 0) { + results = results.concat( + res.errors.map((err: any) => { + const fromPos: { line: number; ch: number } = { + line: err.lineNumber - 1, + ch: err.column - 1, + } + + const toPos: { line: number; ch: number } = { + line: err.lineNumber - 1, + ch: err.column, + } + + return { + from: fromPos, + to: toPos, + message: `[syntax] ${err.description}`, + severity: "error", + } + }) + ) + } + } catch (e) { + const fromPos: { line: number; ch: number } = { + line: e.lineNumber - 1, + ch: e.column - 1, + } + + const toPos: { line: number; ch: number } = { + line: e.lineNumber - 1, + ch: e.column, + } + + results = results.concat([ + { + from: fromPos, + to: toPos, + message: `[syntax] ${e.description}`, + severity: "error", + }, + ]) + } + + return results +} + +export default linter From 33951482d54d2d0b55f10863150039dd5a4b1ec7 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Wed, 1 Sep 2021 17:33:54 +0530 Subject: [PATCH 28/65] feat: placeholder, auto-close brackets, search, line wrap --- components/smart/CodeMirror.vue | 2 ++ helpers/editor/codemirror.ts | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 3cf60203..7298927e 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -39,6 +39,8 @@ const editor = ref(null) useCodemirror(editor, value, { extendedEditorConfig: { mode: props.mode, + placeholder: props.placeholder, + lineWrapping: props.wrap, }, linter: props.linter, }) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 49969267..c9bc7e1d 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -4,6 +4,7 @@ import "codemirror/theme/juejin.css" import "codemirror/lib/codemirror.css" import "codemirror/addon/lint/lint.css" +import "codemirror/addon/dialog/dialog.css" import "codemirror/addon/fold/foldgutter.css" import "codemirror/addon/fold/foldgutter" @@ -12,6 +13,12 @@ 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/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 { watch, onMounted, ref, Ref } from "@nuxtjs/composition-api" import { LinterDefinition } from "./linting/linter" @@ -28,6 +35,7 @@ const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { autoRefresh: true, lineNumbers: true, foldGutter: true, + autoCloseBrackets: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], } From 61da0733c2d5fc0628c066389c5ee156ba266c87 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 1 Sep 2021 20:32:33 +0530 Subject: [PATCH 29/65] feat: codemirror editor options are reactive --- helpers/editor/codemirror.ts | 42 ++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index c9bc7e1d..99d665fd 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -26,7 +26,7 @@ import { LinterDefinition } from "./linting/linter" const DEFAULT_THEME = "juejin" type CodeMirrorOptions = { - extendedEditorConfig: CodeMirror.EditorConfiguration + extendedEditorConfig: Omit linter: LinterDefinition | null } @@ -55,17 +55,34 @@ export function useCodemirror( ) { const cm = ref(null) + 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 updateLinterConfig = () => { + if (options.linter) { + cm.value?.setOption("lint", options.linter) + } + } + // Boot-up CodeMirror, set the value and listeners onMounted(() => { - cm.value = CodeMirror(el.value!, { - ...DEFAULT_EDITOR_CONFIG, - ...options.extendedEditorConfig, - }) - cm.value.setValue(value.value) + cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG) - if (options.linter) { - cm.value.setOption("lint", options.linter) - } + updateEditorConfig() + updateLinterConfig() cm.value.on("change", (instance) => { // External update propagation (via watchers) should be ignored @@ -75,6 +92,13 @@ export function useCodemirror( }) }) + // If the editor properties are reactive, watch for updates + watch(() => options.extendedEditorConfig, updateEditorConfig, { + immediate: true, + deep: true, + }) + watch(() => options.linter, updateLinterConfig, { immediate: true }) + // Watch value updates watch(value, (newVal) => { // Check if we are mounted From 8af90432cf7601f39ad07971fad33b3cb6a6ba46 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Mon, 6 Sep 2021 23:30:43 +0530 Subject: [PATCH 30/65] feat: implement gql query linting in codemirror --- helpers/editor/linting/gqlQuery.ts | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 helpers/editor/linting/gqlQuery.ts diff --git a/helpers/editor/linting/gqlQuery.ts b/helpers/editor/linting/gqlQuery.ts new file mode 100644 index 00000000..648cfa73 --- /dev/null +++ b/helpers/editor/linting/gqlQuery.ts @@ -0,0 +1,58 @@ +import { Ref } from "@nuxtjs/composition-api" +import { + GraphQLError, + GraphQLSchema, + parse as gqlParse, + validate as gqlValidate, +} from "graphql" +import { LinterDefinition, LinterResult } from "./linter" + +/** + * Creates a Linter function that can lint a GQL query against a given + * schema + */ +export const createGQLQueryLinter: ( + schema: Ref +) => LinterDefinition = (schema: Ref) => (text) => { + if (text === "") return Promise.resolve([]) + if (!schema.value) return Promise.resolve([]) + + try { + const doc = gqlParse(text) + + const results = gqlValidate(schema.value, doc).map( + ({ locations, message }) => + { + from: { + line: locations![0].line - 1, + ch: locations![0].column - 1, + }, + to: { + line: locations![0].line - 1, + ch: locations![0].column, + }, + message, + severity: "error", + } + ) + + return Promise.resolve(results) + } catch (e) { + const err = e as GraphQLError + + return Promise.resolve([ + { + from: { + line: err.locations![0].line - 1, + ch: err.locations![0].column - 1, + }, + to: { + line: err.locations![0].line - 1, + ch: err.locations![0].column, + }, + message: err.message, + severity: "error", + }, + ]) + } +} From 2f8aa79ec1f3e84b6a342e7ce3f2c134dba84643 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Mon, 6 Sep 2021 23:47:26 +0530 Subject: [PATCH 31/65] feat: json linter support for codemirror --- helpers/editor/linting/json.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 helpers/editor/linting/json.ts diff --git a/helpers/editor/linting/json.ts b/helpers/editor/linting/json.ts new file mode 100644 index 00000000..46a69019 --- /dev/null +++ b/helpers/editor/linting/json.ts @@ -0,0 +1,21 @@ +import { convertIndexToLineCh } from "../utils" +import { LinterDefinition, LinterResult } from "./linter" +import jsonParse from "~/helpers/jsonParse" + +const linter: LinterDefinition = (text) => { + try { + jsonParse(text) + return Promise.resolve([]) + } catch (e: any) { + return Promise.resolve([ + { + from: convertIndexToLineCh(text, e.start), + to: convertIndexToLineCh(text, e.end), + message: e.message, + severity: "error", + }, + ]) + } +} + +export default linter From 602aabdeb82a52f1154d6b9b4c7dfd7a3199f78b Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 7 Sep 2021 12:14:13 +0530 Subject: [PATCH 32/65] feat: reactive codemirror theme --- assets/scss/styles.scss | 26 +++++++++++++++++++++----- components/smart/CodeMirror.vue | 18 ++---------------- helpers/editor/codemirror.ts | 32 +++++++++++++++++++++++++++----- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss index f54c9535..cfc1a619 100644 --- a/assets/scss/styles.scss +++ b/assets/scss/styles.scss @@ -17,7 +17,7 @@ ::-webkit-scrollbar-thumb { @apply bg-divider bg-clip-content; @apply rounded-full; - @apply border-solid border-4 border-transparent; + @apply border-solid border-transparent border-4; @apply hover:(bg-dividerDark bg-clip-content); } @@ -116,8 +116,8 @@ a { &.link { @apply items-center; - @apply px-1 py-0.5; - @apply -mx-1 -my-0.5; + @apply py-0.5 px-1; + @apply -my-0.5 -mx-1; @apply text-accent; @apply rounded; @apply hover:text-accentDark; @@ -198,7 +198,7 @@ hr { .textarea { @apply flex; @apply w-full; - @apply px-4 py-2; + @apply py-2 px-4; @apply bg-transparent; @apply rounded; @apply text-secondaryDark; @@ -293,7 +293,7 @@ input[type="checkbox"] { @apply cursor-pointer; &::before { - @apply border-2 border-divider; + @apply border-divider border-2; @apply rounded; @apply inline-flex; @apply items-center; @@ -461,6 +461,22 @@ input[type="checkbox"] { @apply w-full; } +.CodeMirror { + @apply block; + @apply border-b; + @apply border-dividerLight; + @apply w-full; + @apply h-auto; +} + +.CodeMirror * { + font-family: "Roboto Mono", monospace; +} + +.CodeMirror-scroll { + @apply min-h-32; +} + @media (max-width: 767px) { main { margin-bottom: env(safe-area-inset-bottom); diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 7298927e..2280af84 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -13,12 +13,13 @@ const props = withDefaults( defineProps<{ value: string mode: string - placeholder: string + placeholder?: string wrap: boolean linter: LinterDefinition | null }>(), { linter: null as any, + placeholder: "", } ) @@ -45,18 +46,3 @@ useCodemirror(editor, value, { linter: props.linter, }) - - diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 99d665fd..fc7c1f97 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -1,6 +1,8 @@ import CodeMirror from "codemirror" -import "codemirror/theme/juejin.css" +import "codemirror/theme/base16-light.css" +import "codemirror/theme/base16-dark.css" +import "codemirror/theme/3024-night.css" import "codemirror/lib/codemirror.css" import "codemirror/addon/lint/lint.css" @@ -20,18 +22,15 @@ import "codemirror/addon/search/searchcursor" import "codemirror/addon/search/jump-to-line" import "codemirror/addon/dialog/dialog" -import { watch, onMounted, ref, Ref } from "@nuxtjs/composition-api" +import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api" import { LinterDefinition } from "./linting/linter" -const DEFAULT_THEME = "juejin" - type CodeMirrorOptions = { extendedEditorConfig: Omit linter: LinterDefinition | null } const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { - theme: DEFAULT_THEME, autoRefresh: true, lineNumbers: true, foldGutter: true, @@ -81,6 +80,7 @@ export function useCodemirror( onMounted(() => { cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG) + setTheme() updateEditorConfig() updateLinterConfig() @@ -92,6 +92,28 @@ export function useCodemirror( }) }) + const setTheme = () => { + const { $colorMode } = useContext() as any + if (cm.value) { + cm.value?.setOption("theme", getThemeName($colorMode.value)) + } + } + + const getThemeName = (mode: string) => { + switch (mode) { + case "system": + return "default" + case "light": + return "base16-light" + case "dark": + return "base16-dark" + case "black": + return "3024-night" + default: + return "default" + } + } + // If the editor properties are reactive, watch for updates watch(() => options.extendedEditorConfig, updateEditorConfig, { immediate: true, From dc5f52cc0dbf834342115d8bf21bc4292e0cfcad Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 7 Sep 2021 16:26:26 +0530 Subject: [PATCH 33/65] refactor: github flavored codemirror light theme --- components/smart/CodeMirror.vue | 5 +++-- helpers/editor/codemirror.ts | 4 ++-- package-lock.json | 11 +++++++++++ package.json | 1 + 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 2280af84..58d8997f 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -14,12 +14,13 @@ const props = withDefaults( value: string mode: string placeholder?: string - wrap: boolean + wrap?: boolean linter: LinterDefinition | null }>(), { - linter: null as any, placeholder: "", + wrap: true, + linter: null as any, } ) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index fc7c1f97..6681f792 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -1,6 +1,6 @@ import CodeMirror from "codemirror" -import "codemirror/theme/base16-light.css" +import "codemirror-github-light/lib/codemirror-github-light-theme.css" import "codemirror/theme/base16-dark.css" import "codemirror/theme/3024-night.css" @@ -104,7 +104,7 @@ export function useCodemirror( case "system": return "default" case "light": - return "base16-light" + return "github-light" case "dark": return "base16-dark" case "black": diff --git a/package-lock.json b/package-lock.json index fa31b90e..6dab0aed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "acorn": "^8.5.0", "acorn-walk": "^8.2.0", "codemirror": "^5.62.3", + "codemirror-github-light": "^0.4.2", "core-js": "^3.17.2", "esprima": "^4.0.1", "firebase": "^9.0.1", @@ -13115,6 +13116,11 @@ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" }, + "node_modules/codemirror-github-light": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/codemirror-github-light/-/codemirror-github-light-0.4.2.tgz", + "integrity": "sha1-iUl8JJWEipRaLNIaqj5Wa4d7FDU=" + }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -46161,6 +46167,11 @@ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" }, + "codemirror-github-light": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/codemirror-github-light/-/codemirror-github-light-0.4.2.tgz", + "integrity": "sha1-iUl8JJWEipRaLNIaqj5Wa4d7FDU=" + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", diff --git a/package.json b/package.json index b121edec..68477db1 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "acorn": "^8.5.0", "acorn-walk": "^8.2.0", "codemirror": "^5.62.3", + "codemirror-github-light": "^0.4.2", "core-js": "^3.17.2", "esprima": "^4.0.1", "firebase": "^9.0.1", From b9fc0175e7d9491982af6df59d0451366509476c Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Tue, 7 Sep 2021 22:12:38 +0530 Subject: [PATCH 34/65] feat: implement base autocomplete implementation for codemirror along with preRequest autocompletion --- components/smart/CodeMirror.vue | 4 +++ helpers/editor/codemirror.ts | 38 +++++++++++++++++++++++++ helpers/editor/completion/index.ts | 33 +++++++++++++++++++++ helpers/editor/completion/preRequest.ts | 30 +++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 helpers/editor/completion/index.ts create mode 100644 helpers/editor/completion/preRequest.ts diff --git a/components/smart/CodeMirror.vue b/components/smart/CodeMirror.vue index 58d8997f..abe3845d 100644 --- a/components/smart/CodeMirror.vue +++ b/components/smart/CodeMirror.vue @@ -8,6 +8,7 @@ import "codemirror/mode/javascript/javascript" import { ref, watch } from "@nuxtjs/composition-api" import { useCodemirror } from "~/helpers/editor/codemirror" import { LinterDefinition } from "~/helpers/editor/linting/linter" +import { Completer } from "~/helpers/editor/completion" const props = withDefaults( defineProps<{ @@ -16,11 +17,13 @@ const props = withDefaults( placeholder?: string wrap?: boolean linter: LinterDefinition | null + completer: Completer | null }>(), { placeholder: "", wrap: true, linter: null as any, + completer: null as any, } ) @@ -45,5 +48,6 @@ useCodemirror(editor, value, { lineWrapping: props.wrap, }, linter: props.linter, + completer: props.completer, }) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 6681f792..852d4659 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -7,6 +7,7 @@ import "codemirror/theme/3024-night.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" @@ -15,6 +16,7 @@ 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" @@ -24,10 +26,12 @@ import "codemirror/addon/dialog/dialog" import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api" import { LinterDefinition } from "./linting/linter" +import { Completer } from "./completion" type CodeMirrorOptions = { extendedEditorConfig: Omit linter: LinterDefinition | null + completer: Completer | null } const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { @@ -76,6 +80,31 @@ export function useCodemirror( } } + 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 result = await options.completer!(text, pos) + + console.log("complete!") + console.log(result) + + return { + from: result.start, + to: result.end, + list: result.completions + .sort((a, b) => a.score - b.score) + .map((x) => x.text), + } + }, + }) + } + } + // Boot-up CodeMirror, set the value and listeners onMounted(() => { cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG) @@ -83,6 +112,7 @@ export function useCodemirror( setTheme() updateEditorConfig() updateLinterConfig() + updateCompleterConfig() cm.value.on("change", (instance) => { // External update propagation (via watchers) should be ignored @@ -90,6 +120,13 @@ export function useCodemirror( value.value = instance.getValue() } }) + + /* TODO: Show autocomplete on typing (this is just for testing) */ + cm.value.on("keyup", (instance, event) => { + if (!instance.state.completionActive && event.key !== "Enter") { + instance.showHint() + } + }) }) const setTheme = () => { @@ -120,6 +157,7 @@ export function useCodemirror( deep: true, }) watch(() => options.linter, updateLinterConfig, { immediate: true }) + watch(() => options.completer, updateCompleterConfig, { immediate: true }) // Watch value updates watch(value, (newVal) => { diff --git a/helpers/editor/completion/index.ts b/helpers/editor/completion/index.ts new file mode 100644 index 00000000..f5f927b0 --- /dev/null +++ b/helpers/editor/completion/index.ts @@ -0,0 +1,33 @@ +export type CompletionEntry = { + text: string + meta: string + score: number +} + +export type CompleterResult = { + /** + * List of completions to display + */ + completions: CompletionEntry[] + /** + * Start of the completion position + * (on completion the start..end region is replaced) + */ + start: { line: number; ch: number } + /** + * End of the completion position + * (on completion the start..end region is replaced) + */ + end: { line: number; ch: number } +} + +export type Completer = ( + /** + * The contents of the editor + */ + text: string, + /** + * Position where the completer is fired + */ + completePos: { line: number; ch: number } +) => Promise diff --git a/helpers/editor/completion/preRequest.ts b/helpers/editor/completion/preRequest.ts new file mode 100644 index 00000000..1a3387cf --- /dev/null +++ b/helpers/editor/completion/preRequest.ts @@ -0,0 +1,30 @@ +import { convertIndexToLineCh } from "../utils" +import { Completer, CompletionEntry } from "." +import { getPreRequestScriptCompletions } from "~/helpers/tern" + +const completer: Completer = async (text, completePos) => { + const results = await getPreRequestScriptCompletions( + text, + completePos.line, + completePos.ch + ) + + const start = convertIndexToLineCh(text, results.start) + const end = convertIndexToLineCh(text, results.end) + + const completions = results.completions.map((completion: any, i: number) => { + return { + text: completion.name, + meta: completion.isKeyword ? "keyword" : completion.type, + score: results.completions.length - i, + } + }) + + return { + start, + end, + completions, + } +} + +export default completer From 8d67a0d95f9dcb396e7fcd5a10425bc72c9532e7 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 04:58:23 +0530 Subject: [PATCH 35/65] feat: test script auto completion for codemirror --- helpers/editor/completion/testScript.ts | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 helpers/editor/completion/testScript.ts diff --git a/helpers/editor/completion/testScript.ts b/helpers/editor/completion/testScript.ts new file mode 100644 index 00000000..94ad4d53 --- /dev/null +++ b/helpers/editor/completion/testScript.ts @@ -0,0 +1,30 @@ +import { convertIndexToLineCh } from "../utils" +import { Completer, CompletionEntry } from "." +import { getTestScriptCompletions } from "~/helpers/tern" + +export const completer: Completer = async (text, completePos) => { + const results = await getTestScriptCompletions( + text, + completePos.line, + completePos.ch + ) + + const start = convertIndexToLineCh(text, results.start) + const end = convertIndexToLineCh(text, results.end) + + const completions = results.completions.map((completion: any, i: number) => { + return { + text: completion.name, + meta: completion.isKeyword ? "keyword" : completion.type, + score: results.completions.length - i, + } + }) + + return { + start, + end, + completions, + } +} + +export default completer From d6e3bd09b46c32d71c03d99c68b4557543e4b908 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 05:36:46 +0530 Subject: [PATCH 36/65] refactor: pass current token position to auto completers on codemirror --- helpers/editor/codemirror.ts | 11 ++++++++--- helpers/editor/completion/index.ts | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 852d4659..3e9fe5d3 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -27,6 +27,7 @@ import "codemirror/addon/dialog/dialog" import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api" import { LinterDefinition } from "./linting/linter" import { Completer } from "./completion" +import { convertIndexToLineCh } from "./utils" type CodeMirrorOptions = { extendedEditorConfig: Omit @@ -88,10 +89,14 @@ export function useCodemirror( const pos = editor.getCursor() const text = editor.getValue() - const result = await options.completer!(text, pos) + const token = editor.getTokenAt(pos) - console.log("complete!") - console.log(result) + const result = await options.completer!(text, pos, { + start: convertIndexToLineCh(text, token.start), + end: convertIndexToLineCh(text, token.end), + }) + + if (!result) return null return { from: result.start, diff --git a/helpers/editor/completion/index.ts b/helpers/editor/completion/index.ts index f5f927b0..d2341444 100644 --- a/helpers/editor/completion/index.ts +++ b/helpers/editor/completion/index.ts @@ -29,5 +29,9 @@ export type Completer = ( /** * Position where the completer is fired */ - completePos: { line: number; ch: number } -) => Promise + completePos: { line: number; ch: number }, + completeTokenLocation: { + start: { line: number; ch: number } + end: { line: number; ch: number } + } +) => Promise From 639a629809f26cb1519f743742ff4509e5fb50c5 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 05:38:03 +0530 Subject: [PATCH 37/65] feat: gql query autocompletion on codemirror --- helpers/editor/completion/gqlQuery.ts | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 helpers/editor/completion/gqlQuery.ts diff --git a/helpers/editor/completion/gqlQuery.ts b/helpers/editor/completion/gqlQuery.ts new file mode 100644 index 00000000..3ff56d13 --- /dev/null +++ b/helpers/editor/completion/gqlQuery.ts @@ -0,0 +1,29 @@ +import { Ref } from "@nuxtjs/composition-api" +import { GraphQLSchema } from "graphql" +import { getAutocompleteSuggestions } from "graphql-language-service-interface" +import { Completer, CompleterResult, CompletionEntry } from "." + +const completer: (schemaRef: Ref) => Completer = + (schemaRef: Ref) => (text, completePos, tokenPos) => { + if (!schemaRef.value) return Promise.resolve(null) + + const completions = getAutocompleteSuggestions(schemaRef.value, text, { + line: completePos.line, + character: completePos.ch, + } as any) + + return Promise.resolve({ + start: tokenPos.start, + end: tokenPos.end, + completions: completions.map( + (x, i) => + { + text: x.label!, + meta: x.detail!, + score: completions.length - i, + } + ), + }) + } + +export default completer From 4c55b9c304da75c84cec78db17bb8426a1ad55c6 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 06:00:23 +0530 Subject: [PATCH 38/65] fix: codemirror theme not changing when color mode is updated --- helpers/editor/codemirror.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 3e9fe5d3..bd5e8a99 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -57,6 +57,8 @@ export function useCodemirror( value: Ref, options: CodeMirrorOptions ) { + const { $colorMode } = useContext() as any + const cm = ref(null) const updateEditorConfig = () => { @@ -135,7 +137,6 @@ export function useCodemirror( }) const setTheme = () => { - const { $colorMode } = useContext() as any if (cm.value) { cm.value?.setOption("theme", getThemeName($colorMode.value)) } @@ -175,6 +176,9 @@ export function useCodemirror( } }) + // Watch color mode updates and update theme + watch(() => $colorMode.value, setTheme) + return { cm, } From 2bb3b71a704a1790c99da15baf1b4a160c668b19 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Wed, 8 Sep 2021 19:51:43 +0530 Subject: [PATCH 39/65] refactor: map ctrl-space to autocomplete by default in codemirror --- helpers/editor/codemirror.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index bd5e8a99..e1e8791a 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -41,6 +41,9 @@ const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { foldGutter: true, autoCloseBrackets: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], + extraKeys: { + "Ctrl-Space": "autocomplete", + } } /** @@ -128,12 +131,12 @@ export function useCodemirror( } }) - /* TODO: Show autocomplete on typing (this is just for testing) */ - cm.value.on("keyup", (instance, event) => { - if (!instance.state.completionActive && event.key !== "Enter") { - instance.showHint() - } - }) + // /* TODO: Show autocomplete on typing (this is just for testing) */ + // cm.value.on("keyup", (instance, event) => { + // if (!instance.state.completionActive && event.key !== "Enter") { + // instance.showHint() + // } + // }) }) const setTheme = () => { From e1a25fa894875701f5d991ff51e5497590f8ff01 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Wed, 8 Sep 2021 20:27:36 +0530 Subject: [PATCH 40/65] fix: broken conditional rendering of codemirror Co-authored-by: Andrew Bastin --- helpers/editor/codemirror.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index e1e8791a..487365c4 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -43,7 +43,7 @@ const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], extraKeys: { "Ctrl-Space": "autocomplete", - } + }, } /** @@ -115,8 +115,9 @@ export function useCodemirror( } } - // Boot-up CodeMirror, set the value and listeners - onMounted(() => { + const initialize = () => { + if (!el.value) return + cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG) setTheme() @@ -130,15 +131,16 @@ export function useCodemirror( value.value = instance.getValue() } }) + } - // /* TODO: Show autocomplete on typing (this is just for testing) */ - // cm.value.on("keyup", (instance, event) => { - // if (!instance.state.completionActive && event.key !== "Enter") { - // instance.showHint() - // } - // }) + // Boot-up CodeMirror, set the value and listeners + onMounted(() => { + initialize() }) + // Reinitialize if the target ref updates + watch(el, initialize) + const setTheme = () => { if (cm.value) { cm.value?.setOption("theme", getThemeName($colorMode.value)) From b15fd6c75a6e540efa592b33720f517a8dd24b16 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Wed, 8 Sep 2021 21:52:26 +0530 Subject: [PATCH 41/65] feat: port bulk editor textareas to codemirror --- assets/scss/styles.scss | 3 +- components/graphql/RequestOptions.vue | 376 +++++++++++--------------- components/http/Headers.vue | 166 +++++------- components/http/Parameters.vue | 162 +++++------ 4 files changed, 303 insertions(+), 404 deletions(-) diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss index cfc1a619..a08772cd 100644 --- a/assets/scss/styles.scss +++ b/assets/scss/styles.scss @@ -36,7 +36,8 @@ } input::placeholder, -textarea::placeholder { +textarea::placeholder, +.CodeMirror-empty { @apply text-secondaryDark; @apply opacity-25; } diff --git a/components/graphql/RequestOptions.vue b/components/graphql/RequestOptions.vue index 7113b4e4..2c4863d3 100644 --- a/components/graphql/RequestOptions.vue +++ b/components/graphql/RequestOptions.vue @@ -42,9 +42,7 @@ /> @@ -174,25 +172,7 @@
- +
- diff --git a/components/http/Headers.vue b/components/http/Headers.vue index 3294219b..5fff7dbf 100644 --- a/components/http/Headers.vue +++ b/components/http/Headers.vue @@ -48,25 +48,7 @@
- +
- diff --git a/components/http/Parameters.vue b/components/http/Parameters.vue index 04a7d9ad..1b24cb6d 100644 --- a/components/http/Parameters.vue +++ b/components/http/Parameters.vue @@ -48,25 +48,7 @@
- +
-import { - defineComponent, - ref, - useContext, - watch, -} from "@nuxtjs/composition-api" + From f4f74e223f1162450a5f4822a31ef0786ac228e5 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Thu, 9 Sep 2021 01:03:46 +0530 Subject: [PATCH 42/65] refactor: a volar types shim generator --- .gitignore | 3 + modules/emit-volar-types.ts | 129 ++++++++++++++++++++++++++++++++++++ nuxt.config.js | 1 + 3 files changed, 133 insertions(+) create mode 100644 modules/emit-volar-types.ts diff --git a/.gitignore b/.gitignore index 9d566a67..211118bf 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,6 @@ tests/*/screenshots # Tests videos tests/*/videos + +# Andrew's crazy Volar shim generator +shims-volar.d.ts diff --git a/modules/emit-volar-types.ts b/modules/emit-volar-types.ts new file mode 100644 index 00000000..bd130d3e --- /dev/null +++ b/modules/emit-volar-types.ts @@ -0,0 +1,129 @@ +import { resolve } from "path" +import { Module } from "@nuxt/types" +import ts from "typescript" + +const { readdir, writeFile } = require("fs").promises + +function titleCase(str: string): string { + return str[0].toUpperCase() + str.substring(1) +} + +async function* getFilesInDir(dir: string): AsyncIterable { + const dirents = await readdir(dir, { withFileTypes: true }) + for (const dirent of dirents) { + const res = resolve(dir, dirent.name) + if (dirent.isDirectory()) { + yield* getFilesInDir(res) + } else { + yield res + } + } +} + +async function getAllVueComponentPaths(): Promise { + const vueFilePaths: string[] = [] + + for await (const f of getFilesInDir("./components")) { + if (f.endsWith(".vue")) { + const componentsIndex = f.split("/").indexOf("components") + + vueFilePaths.push( + `./${f + .split("/") + .slice(componentsIndex + 1) + .join("/")}` + ) + } + } + + return vueFilePaths +} + +function resolveComponentName(filename: string): string { + const index = filename.split("/").indexOf("components") + + return filename + .split("/") + .slice(index + 1) + .map((x) => x.split(".vue")[0]) // Remove extension + .filter((x) => x.toUpperCase() !== x.toLowerCase()) + .map((x) => titleCase(x)) // titlecase it + .join("") +} + +function createTSImports(components: [string, string][]) { + return components.map(([componentName, componentPath]) => { + return ts.factory.createImportDeclaration( + undefined, + undefined, + ts.factory.createImportClause( + false, + ts.factory.createIdentifier(componentName), + undefined + ), + ts.factory.createStringLiteral(componentPath) + ) + }) +} + +function createTSProps(components: [string, string][]) { + return components.map(([componentName]) => { + return ts.factory.createPropertySignature( + undefined, + ts.factory.createIdentifier(componentName), + undefined, + ts.factory.createTypeQueryNode(ts.factory.createIdentifier(componentName)) + ) + }) +} + +function generateTypeScriptDef(components: [string, string][]) { + const statements = [ + ...createTSImports(components), + ts.factory.createModuleDeclaration( + undefined, + [ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword)], + ts.factory.createIdentifier("global"), + ts.factory.createModuleBlock([ + ts.factory.createInterfaceDeclaration( + undefined, + undefined, + ts.factory.createIdentifier("__VLS_GlobalComponents"), + undefined, + undefined, + [...createTSProps(components)] + ), + ]), + ts.NodeFlags.ExportContext | + ts.NodeFlags.GlobalAugmentation | + ts.NodeFlags.ContextFlags + ), + ] + + const source = ts.factory.createSourceFile( + statements, + ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), + ts.NodeFlags.None + ) + + const printer = ts.createPrinter({ + newLine: ts.NewLineKind.LineFeed, + }) + + return printer.printFile(source) +} + +const module: Module<{}> = async function () { + if (!this.nuxt.options.dev) return + + const results = await getAllVueComponentPaths() + const fileComponentNameCombo: [string, string][] = results.map((x) => [ + resolveComponentName(x), + x, + ]) + const typescriptString = generateTypeScriptDef(fileComponentNameCombo) + + await writeFile(resolve("shims-volar.d.ts"), typescriptString) +} + +export default module diff --git a/nuxt.config.js b/nuxt.config.js index b77f39f7..c053be3e 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -133,6 +133,7 @@ export default { "@nuxtjs/composition-api/module", // https://github.com/antfu/unplugin-vue2-script-setup "unplugin-vue2-script-setup/nuxt", + "~/modules/emit-volar-types.ts", ], // Modules (https://go.nuxtjs.dev/config-modules) From 4a12cc76fa485cc5ac0d0c2d106112189d18e4d9 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Thu, 9 Sep 2021 01:56:43 +0530 Subject: [PATCH 43/65] feat: improve shim generation for index components --- modules/emit-volar-types.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/modules/emit-volar-types.ts b/modules/emit-volar-types.ts index bd130d3e..c1ecb4f9 100644 --- a/modules/emit-volar-types.ts +++ b/modules/emit-volar-types.ts @@ -1,6 +1,7 @@ import { resolve } from "path" import { Module } from "@nuxt/types" import ts from "typescript" +import chokidar from "chokidar" const { readdir, writeFile } = require("fs").promises @@ -30,7 +31,7 @@ async function getAllVueComponentPaths(): Promise { vueFilePaths.push( `./${f .split("/") - .slice(componentsIndex + 1) + .slice(componentsIndex) .join("/")}` ) } @@ -45,8 +46,9 @@ function resolveComponentName(filename: string): string { return filename .split("/") .slice(index + 1) + .filter((x) => x !== "index.vue") // Remove index.vue .map((x) => x.split(".vue")[0]) // Remove extension - .filter((x) => x.toUpperCase() !== x.toLowerCase()) + .filter((x) => x.toUpperCase() !== x.toLowerCase()) // Remove non-word stuff .map((x) => titleCase(x)) // titlecase it .join("") } @@ -113,9 +115,7 @@ function generateTypeScriptDef(components: [string, string][]) { return printer.printFile(source) } -const module: Module<{}> = async function () { - if (!this.nuxt.options.dev) return - +async function generateShim() { const results = await getAllVueComponentPaths() const fileComponentNameCombo: [string, string][] = results.map((x) => [ resolveComponentName(x), @@ -126,4 +126,14 @@ const module: Module<{}> = async function () { await writeFile(resolve("shims-volar.d.ts"), typescriptString) } +const module: Module<{}> = async function () { + if (!this.nuxt.options.dev) return + + await generateShim() + + chokidar.watch(resolve("../components/")).on("all", async () => { + await generateShim() + }) +} + export default module From 02cf6200901f5e2c587eab364869f11e88d91499 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Thu, 9 Sep 2021 17:47:27 +0530 Subject: [PATCH 44/65] feat: port ace editor to codemirror Co-authored-by: Andrew Bastin --- assets/scss/styles.scss | 2 +- components/graphql/Request.vue | 49 ++--- components/graphql/RequestOptions.vue | 25 ++- components/graphql/Response.vue | 139 +++++--------- components/http/ResponseMeta.vue | 64 ++----- .../lenses/renderers/HTMLLensRenderer.vue | 175 ++++++++++-------- .../lenses/renderers/JSONLensRenderer.vue | 158 ++++++++-------- .../lenses/renderers/RawLensRenderer.vue | 149 ++++++++------- .../lenses/renderers/XMLLensRenderer.vue | 149 ++++++++------- helpers/editor/codemirror.ts | 12 +- 10 files changed, 437 insertions(+), 485 deletions(-) diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss index a08772cd..b253cd68 100644 --- a/assets/scss/styles.scss +++ b/assets/scss/styles.scss @@ -467,7 +467,7 @@ input[type="checkbox"] { @apply border-b; @apply border-dividerLight; @apply w-full; - @apply h-auto; + @apply !h-full; } .CodeMirror * { diff --git a/components/graphql/Request.vue b/components/graphql/Request.vue index a84a1645..304ca648 100644 --- a/components/graphql/Request.vue +++ b/components/graphql/Request.vue @@ -33,45 +33,32 @@
- diff --git a/components/graphql/RequestOptions.vue b/components/graphql/RequestOptions.vue index 2c4863d3..e8c7147f 100644 --- a/components/graphql/RequestOptions.vue +++ b/components/graphql/RequestOptions.vue @@ -106,19 +106,7 @@ />
- +
@@ -323,6 +311,7 @@ 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" const props = defineProps<{ conn: GQLConnection @@ -370,6 +359,16 @@ useCodemirror(bulkEditor, bulkHeaders, { completer: null, }) +const variableEditor = ref(null) + +useCodemirror(variableEditor, variableString, { + extendedEditorConfig: { + mode: "javascript", + }, + linter: null, + completer: null, +}) + const queryEditor = ref(null) const copyQueryIcon = ref("copy") diff --git a/components/graphql/Response.vue b/components/graphql/Response.vue index feca15fc..09c6be4e 100644 --- a/components/graphql/Response.vue +++ b/components/graphql/Response.vue @@ -59,38 +59,6 @@ justify-center " > -
-
- - {{ $t("shortcut.request.send_request") }} - - - {{ $t("shortcut.general.show_all") }} - - -
-
-
- {{ getSpecialKey() }} - G -
-
- {{ getSpecialKey() }} - K -
- -
-
- diff --git a/components/lenses/renderers/JSONLensRenderer.vue b/components/lenses/renderers/JSONLensRenderer.vue index fc6bc2f1..c1ea9347 100644 --- a/components/lenses/renderers/JSONLensRenderer.vue +++ b/components/lenses/renderers/JSONLensRenderer.vue @@ -36,88 +36,92 @@
- +
- diff --git a/components/lenses/renderers/RawLensRenderer.vue b/components/lenses/renderers/RawLensRenderer.vue index 563f9de1..ec5b015a 100644 --- a/components/lenses/renderers/RawLensRenderer.vue +++ b/components/lenses/renderers/RawLensRenderer.vue @@ -36,79 +36,92 @@
- +
- diff --git a/components/lenses/renderers/XMLLensRenderer.vue b/components/lenses/renderers/XMLLensRenderer.vue index eb5340f4..11a6463a 100644 --- a/components/lenses/renderers/XMLLensRenderer.vue +++ b/components/lenses/renderers/XMLLensRenderer.vue @@ -36,79 +36,92 @@
- +
- diff --git a/helpers/editor/codemirror.ts b/helpers/editor/codemirror.ts index 487365c4..e7781ae7 100644 --- a/helpers/editor/codemirror.ts +++ b/helpers/editor/codemirror.ts @@ -44,6 +44,7 @@ const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = { extraKeys: { "Ctrl-Space": "autocomplete", }, + viewportMargin: Infinity, } /** @@ -120,6 +121,8 @@ export function useCodemirror( cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG) + cm.value.setValue(value.value) + setTheme() updateEditorConfig() updateLinterConfig() @@ -139,7 +142,14 @@ export function useCodemirror( }) // Reinitialize if the target ref updates - watch(el, initialize) + watch(el, () => { + if (cm.value) { + const parent = cm.value.getWrapperElement() + parent.remove() + cm.value = null + } + initialize() + }) const setTheme = () => { if (cm.value) { From c6c08f6c60331085cac4c0d9c0527bd3a754c468 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Thu, 9 Sep 2021 20:50:04 +0530 Subject: [PATCH 45/65] feat: reactive line wrap on codemirror Co-authored-by: Andrew Bastin --- assets/icons/corner-down-left.svg | 1 + assets/scss/styles.scss | 2 +- .../lenses/renderers/HTMLLensRenderer.vue | 32 +++++++++++++------ .../lenses/renderers/JSONLensRenderer.vue | 32 +++++++++++++------ .../lenses/renderers/RawLensRenderer.vue | 32 +++++++++++++------ .../lenses/renderers/XMLLensRenderer.vue | 32 +++++++++++++------ locales/en.json | 1 + 7 files changed, 95 insertions(+), 37 deletions(-) create mode 100644 assets/icons/corner-down-left.svg diff --git a/assets/icons/corner-down-left.svg b/assets/icons/corner-down-left.svg new file mode 100644 index 00000000..7a8b7473 --- /dev/null +++ b/assets/icons/corner-down-left.svg @@ -0,0 +1 @@ + diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss index b253cd68..8b826e2e 100644 --- a/assets/scss/styles.scss +++ b/assets/scss/styles.scss @@ -475,7 +475,7 @@ input[type="checkbox"] { } .CodeMirror-scroll { - @apply min-h-32; + @apply min-h-64; } @media (max-width: 767px) { diff --git a/components/lenses/renderers/HTMLLensRenderer.vue b/components/lenses/renderers/HTMLLensRenderer.vue index f3a751de..d7f1044c 100644 --- a/components/lenses/renderers/HTMLLensRenderer.vue +++ b/components/lenses/renderers/HTMLLensRenderer.vue @@ -17,6 +17,14 @@ {{ $t("response.body") }}
+ diff --git a/helpers/editorutils.js b/helpers/editorutils.js index 0955bbf3..9dd46a4b 100644 --- a/helpers/editorutils.js +++ b/helpers/editorutils.js @@ -1,12 +1,12 @@ const mimeToMode = { - "text/plain": "plain_text", - "text/html": "html", - "application/xml": "xml", - "application/hal+json": "json", - "application/vnd.api+json": "json", - "application/json": "json", + "text/plain": "text/x-yaml", + "text/html": "htmlmixed", + "application/xml": "application/xml", + "application/hal+json": "application/ld+json", + "application/vnd.api+json": "application/ld+json", + "application/json": "application/ld+json", } export function getEditorLangForMimeType(mimeType) { - return mimeToMode[mimeType] || "plain_text" + return mimeToMode[mimeType] || "text/x-yaml" } From 2eb0a4c754d0223442cd652f58cd4d7323294a8d Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Fri, 10 Sep 2021 10:46:58 +0530 Subject: [PATCH 48/65] feat: reactive syntax + line wrap on raw body --- components/http/RawBody.vue | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/components/http/RawBody.vue b/components/http/RawBody.vue index 6a440f46..1b31936d 100644 --- a/components/http/RawBody.vue +++ b/components/http/RawBody.vue @@ -24,6 +24,13 @@ :title="$t('app.wiki')" svg="help-circle" /> + diff --git a/components/http/Tests.vue b/components/http/Tests.vue index a44afcc8..2cba8649 100644 --- a/components/http/Tests.vue +++ b/components/http/Tests.vue @@ -24,6 +24,13 @@ :title="$t('app.wiki')" svg="help-circle" /> +
- +
+import { reactive, ref } 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" const testScript = useTestScript() +const testScriptEditor = ref(null) +const linewrapEnabled = ref(true) + +useCodemirror( + testScriptEditor, + testScript, + reactive({ + extendedEditorConfig: { + mode: "application/javascript", + lineWrapping: linewrapEnabled, + }, + linter, + completer, + }) +) + const useSnippet = (script: string) => { testScript.value += script } From 457b6b982cfed04c9ab60824f49cb73876194cba Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Fri, 10 Sep 2021 16:12:04 +0530 Subject: [PATCH 50/65] feat: codemirror for graphql query, scheme and response --- components/graphql/RequestOptions.vue | 45 ++-- components/graphql/Response.vue | 51 ++-- components/graphql/Sidebar.vue | 356 ++++++++++++-------------- layouts/default.vue | 2 +- 4 files changed, 219 insertions(+), 235 deletions(-) diff --git a/components/graphql/RequestOptions.vue b/components/graphql/RequestOptions.vue index e8c7147f..31da9123 100644 --- a/components/graphql/RequestOptions.vue +++ b/components/graphql/RequestOptions.vue @@ -55,20 +55,7 @@ />
- +
@@ -284,6 +271,7 @@ diff --git a/layouts/default.vue b/layouts/default.vue index 5bc5bfdc..54456805 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -24,7 +24,7 @@ >
- +
From b4ed6fd10782d77c82e5813a84798772808dc011 Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Fri, 10 Sep 2021 18:27:31 +0530 Subject: [PATCH 51/65] feat: codemirror for import curl and codegens --- assets/scss/styles.scss | 2 - components/http/CodegenModal.vue | 28 +++-- components/http/ImportCurl.vue | 209 ++++++++++++++++--------------- helpers/codegen/codegen.ts | 2 - 4 files changed, 121 insertions(+), 120 deletions(-) diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss index 8b826e2e..16aa0688 100644 --- a/assets/scss/styles.scss +++ b/assets/scss/styles.scss @@ -464,8 +464,6 @@ input[type="checkbox"] { .CodeMirror { @apply block; - @apply border-b; - @apply border-dividerLight; @apply w-full; @apply !h-full; } diff --git a/components/http/CodegenModal.vue b/components/http/CodegenModal.vue index fe3c39ce..029b28f1 100644 --- a/components/http/CodegenModal.vue +++ b/components/http/CodegenModal.vue @@ -38,21 +38,11 @@ {{ t("request.generated_code") }}
- + class="w-full border border-dividerLight rounded block" + > diff --git a/components/http/ImportCurl.vue b/components/http/ImportCurl.vue index 422f7899..67154159 100644 --- a/components/http/ImportCurl.vue +++ b/components/http/ImportCurl.vue @@ -13,7 +13,7 @@