diff --git a/packages/hoppscotch-app/helpers/editor/codemirror.ts b/packages/hoppscotch-app/helpers/editor/codemirror.ts index 403add752..0ae6fb63d 100644 --- a/packages/hoppscotch-app/helpers/editor/codemirror.ts +++ b/packages/hoppscotch-app/helpers/editor/codemirror.ts @@ -30,16 +30,20 @@ import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api" import { EditorState, Compartment, - StateField, EditorSelection, TransactionSpec, + Extension, } from "@codemirror/state" import { EditorView, keymap, ViewPlugin, ViewUpdate } from "@codemirror/view" import { defaultKeymap } from "@codemirror/commands" import { basicSetup } from "@codemirror/basic-setup" -import { javascript } from "@codemirror/lang-javascript" -import { json } from "@codemirror/lang-json" +import { javascriptLanguage } from "@codemirror/lang-javascript" +import { Language, LanguageSupport } from "@codemirror/language" +import { linter } from "@codemirror/lint" +import { jsonLanguage } from "@codemirror/lang-json" import { onBeforeUnmount } from "@vue/runtime-dom" +import { pipe } from "fp-ts/function" +import * as O from "fp-ts/Option" import { isJSONContentType } from "../utils/contenttypes" import { Completer } from "./completion" import { LinterDefinition } from "./linting/linter" @@ -229,21 +233,56 @@ export function useCodemirror( } } -const getEditorLanguage = (mode: string) => { - if (isJSONContentType(mode)) { - return json() - } else if (mode === "application/javascript") { - return javascript() - } else { - return StateField.define({ - create() { - return null - }, - update() {}, +const hoppLinterExt = (hoppLinter: LinterDefinition): Extension => { + return linter(async (view) => { + // Requires full document scan, hence expensive on big files, force disable on big files ? + const linterResult = await hoppLinter( + view.state.doc.toJSON().join(view.state.lineBreak) + ) + + return linterResult.map((result) => { + const startPos = + view.state.doc.line(result.from.line + 1).from + result.from.ch + const endPos = view.state.doc.line(result.to.line + 1).from + result.to.ch + + return { + from: startPos, + to: endPos, + message: result.message, + severity: result.severity, + } }) - } + }) } +const hoppLang = ( + language: Language, + linter?: LinterDefinition | undefined +) => { + return new LanguageSupport(language, linter ? [hoppLinterExt(linter)] : []) +} + +const getLanguage = (langMime: string): Language | null => { + if (isJSONContentType(langMime)) { + return jsonLanguage + } else if (langMime === "application/javascript") { + return javascriptLanguage + } + + // None matched, so return null + return null +} + +const getEditorLanguage = ( + langMime: string, + linter: LinterDefinition | undefined +): Extension => + pipe( + O.fromNullable(getLanguage(langMime)), + O.map((lang) => hoppLang(lang, linter)), + O.getOrElseW(() => []) + ) + export function useNewCodemirror( el: Ref, value: Ref, @@ -297,7 +336,10 @@ export function useNewCodemirror( ), EditorState.changeFilter.of(() => !options.extendedEditorConfig.readOnly), language.of( - getEditorLanguage((options.extendedEditorConfig.mode as any) ?? "") + getEditorLanguage( + (options.extendedEditorConfig.mode as any) ?? "", + options.linter ?? undefined + ) ), lineWrapping.of( options.extendedEditorConfig.lineWrapping @@ -354,11 +396,14 @@ export function useNewCodemirror( ) watch( - () => options.extendedEditorConfig.mode, - (newMode) => { + () => [options.extendedEditorConfig.mode, options.linter], + () => { dispatch({ effects: language.reconfigure( - getEditorLanguage((newMode as any) ?? "") + getEditorLanguage( + (options.extendedEditorConfig.mode as any) ?? "", + options.linter ?? undefined + ) ), }) } diff --git a/packages/hoppscotch-app/package.json b/packages/hoppscotch-app/package.json index e2e10af32..43f53fec1 100644 --- a/packages/hoppscotch-app/package.json +++ b/packages/hoppscotch-app/package.json @@ -40,6 +40,7 @@ "@codemirror/lang-javascript": "^0.19.2", "@codemirror/lang-json": "^0.19.1", "@codemirror/language": "^0.19.3", + "@codemirror/lint": "^0.19.2", "@codemirror/state": "^0.19.3", "@codemirror/text": "^0.19.5", "@codemirror/view": "^0.19.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47ca7b22c..b98b58718 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,7 @@ importers: '@codemirror/lang-javascript': ^0.19.2 '@codemirror/lang-json': ^0.19.1 '@codemirror/language': ^0.19.3 + '@codemirror/lint': ^0.19.2 '@codemirror/state': ^0.19.3 '@codemirror/text': ^0.19.5 '@codemirror/view': ^0.19.12 @@ -140,6 +141,7 @@ importers: '@codemirror/lang-javascript': 0.19.2 '@codemirror/lang-json': 0.19.1 '@codemirror/language': 0.19.3 + '@codemirror/lint': 0.19.2 '@codemirror/state': 0.19.3 '@codemirror/text': 0.19.5 '@codemirror/view': 0.19.12 @@ -3912,8 +3914,8 @@ packages: ufo: 0.7.9 dev: false - /@nuxt/kit-edge/3.0.0-27267816.6bd7186: - resolution: {integrity: sha512-OCinQR1TBeZmgU61of/YM55JvY+3emO6D4BwfTNYe8KwqIpynYcrZJxlxAYRYZKzqfZhtqLnD1B8DJ1f2b6thQ==} + /@nuxt/kit-edge/3.0.0-27268729.5b8e10f: + resolution: {integrity: sha512-m7bzSe8NRuR7ZcZqKwBQfOb1y3AlmGIiNMibZVO4r0ggIfT4fmkzVwfdZm+oWNhOYlZET7Kyk08rSjwShENukQ==} engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0} dependencies: consola: 2.15.3 @@ -3934,7 +3936,7 @@ packages: std-env: 3.0.0 ufo: 0.7.9 unctx: 1.0.2 - untyped: 0.2.11 + untyped: 0.2.12 dev: true /@nuxt/loading-screen/2.0.4: @@ -14036,7 +14038,7 @@ packages: /nuxt-windicss/2.0.11: resolution: {integrity: sha512-/vAEmKLq1Iomuj4lz751dsoXdlGVAoiEGSh3JVxuZJMkqc/yrHTQrNhtMaOQzx5heuVsQ+E2bIF+Q/tfxicOFQ==} dependencies: - '@nuxt/kit': /@nuxt/kit-edge/3.0.0-27267816.6bd7186 + '@nuxt/kit': /@nuxt/kit-edge/3.0.0-27268729.5b8e10f defu: 5.0.0 h3: 0.3.3 listhen: 0.2.5 @@ -18167,8 +18169,8 @@ packages: has-value: 0.3.1 isobject: 3.0.1 - /untyped/0.2.11: - resolution: {integrity: sha512-KVNcu9jB+mlnQJiunAzmqpnnn9R+yniT+AkOk9ZgCIsThwh0nlP6wO+O7mJjHM7Y2yplEu3v6NNtRvb82+uGxw==} + /untyped/0.2.12: + resolution: {integrity: sha512-mdMpwUHnJUQDpEmuByMuLxYdrPVlA98a1/b8MgoFoasR5tJTfsNbhAPfceAgFqMd/E05427T7MXgJGqdtDF/bQ==} dev: true /upath/1.2.0: