From 8c9cd079b783e75cf889668069178b9d97af74ea Mon Sep 17 00:00:00 2001 From: liyasthomas Date: Tue, 31 Aug 2021 00:03:07 +0530 Subject: [PATCH 01/20] 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 000000000..f4dfc315f --- /dev/null +++ b/components/smart/CodeMirror.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/package-lock.json b/package-lock.json index 13bea05f5..bb3502c93 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 1bfe4b707..867955c4f 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/20] 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 f4dfc315f..f29fe92a3 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 bb3502c93..f309be5db 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 867955c4f..59182f6aa 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/20] 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 f29fe92a3..292732f11 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/20] 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 292732f11..6068778c8 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 000000000..9a282979f --- /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/20] 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 ee7fdab33..a456b63a0 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 f1b22c2d9..45a291014 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/20] 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 6068778c8..3cf602030 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 9a282979f..49969267f 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 000000000..704270cbe --- /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 000000000..18b311765 --- /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/20] 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 000000000..db42d9864 --- /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 000000000..902d1778c --- /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/20] 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 3cf602030..7298927ee 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 49969267f..c9bc7e1df 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/20] 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 c9bc7e1df..99d665fd9 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/20] 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 000000000..648cfa732 --- /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/20] 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 000000000..46a690197 --- /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/20] 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 f54c9535e..cfc1a6192 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 7298927ee..2280af842 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 99d665fd9..fc7c1f976 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/20] 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 2280af842..58d8997fd 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 fc7c1f976..6681f792a 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 a456b63a0..bff973671 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 45a291014..1e684e937 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/20] 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 58d8997fd..abe3845de 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 6681f792a..852d46598 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 000000000..f5f927b06 --- /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 000000000..1a3387cff --- /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/20] 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 000000000..94ad4d53e --- /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/20] 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 852d46598..3e9fe5d33 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 f5f927b06..d23414446 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/20] 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 000000000..3ff56d135 --- /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/20] 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 3e9fe5d33..bd5e8a991 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/20] 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 bd5e8a991..e1e8791a3 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/20] 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 e1e8791a3..487365c49 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))