🚧 Added HTTP language parser
This commit is contained in:
333
frontend/package-lock.json
generated
333
frontend/package-lock.json
generated
@@ -80,6 +80,7 @@
|
||||
"unplugin-vue-components": "^30.0.0",
|
||||
"vite": "^7.1.12",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"vitest": "^4.0.4",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.1.1"
|
||||
}
|
||||
@@ -2293,6 +2294,13 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@toml-tools/lexer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@toml-tools/lexer/-/lexer-1.0.0.tgz",
|
||||
@@ -2312,6 +2320,24 @@
|
||||
"chevrotain": "^11.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chai": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz",
|
||||
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/deep-eql": "*",
|
||||
"assertion-error": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/deep-eql": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -2618,6 +2644,127 @@
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-4.0.4.tgz",
|
||||
"integrity": "sha512-0ioMscWJtfpyH7+P82sGpAi3Si30OVV73jD+tEqXm5+rIx9LgnfdaOn45uaFkKOncABi/PHL00Yn0oW/wK4cXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.0.4",
|
||||
"@vitest/utils": "4.0.4",
|
||||
"chai": "^6.0.1",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-4.0.4.tgz",
|
||||
"integrity": "sha512-UTtKgpjWj+pvn3lUM55nSg34098obGhSHH+KlJcXesky8b5wCUgg7s60epxrS6yAG8slZ9W8T9jGWg4PisMf5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.0.4",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.19"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"msw": "^2.4.9",
|
||||
"vite": "^6.0.0 || ^7.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"msw": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.0.4.tgz",
|
||||
"integrity": "sha512-lHI2rbyrLVSd1TiHGJYyEtbOBo2SDndIsN3qY4o4xe2pBxoJLD6IICghNCvD7P+BFin6jeyHXiUICXqgl6vEaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-4.0.4.tgz",
|
||||
"integrity": "sha512-99EDqiCkncCmvIZj3qJXBZbyoQ35ghOwVWNnQ5nj0Hnsv4Qm40HmrMJrceewjLVvsxV/JSU4qyx2CGcfMBmXJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.0.4",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-4.0.4.tgz",
|
||||
"integrity": "sha512-XICqf5Gi4648FGoBIeRgnHWSNDp+7R5tpclGosFaUUFzY6SfcpsfHNMnC7oDu/iOLBxYfxVzaQpylEvpgii3zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.4",
|
||||
"magic-string": "^0.30.19",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-4.0.4.tgz",
|
||||
"integrity": "sha512-G9L13AFyYECo40QG7E07EdYnZZYCKMTSp83p9W8Vwed0IyCG1GnpDLxObkx8uOGPXfDpdeVf24P1Yka8/q1s9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-4.0.4.tgz",
|
||||
"integrity": "sha512-4bJLmSvZLyVbNsYFRpPYdJViG9jZyRvMZ35IF4ymXbRZoS+ycYghmwTGiscTXduUg2lgKK7POWIyXJNute1hjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.0.4",
|
||||
"tinyrainbow": "^3.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.23",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.23.tgz",
|
||||
@@ -2945,6 +3092,16 @@
|
||||
"util": "^0.12.5"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/autolinker": {
|
||||
"version": "3.16.2",
|
||||
"resolved": "https://registry.npmmirror.com/autolinker/-/autolinker-3.16.2.tgz",
|
||||
@@ -3325,6 +3482,16 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/chai/-/chai-6.2.0.tgz",
|
||||
"integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -3902,6 +4069,13 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
@@ -4182,6 +4356,16 @@
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/expect-type": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/expect-type/-/expect-type-1.2.2.tgz",
|
||||
"integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.7.tgz",
|
||||
@@ -6298,6 +6482,13 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/siginfo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz",
|
||||
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -6322,13 +6513,19 @@
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz",
|
||||
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.9.0.tgz",
|
||||
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stream-browserify": {
|
||||
"version": "3.0.0",
|
||||
@@ -6448,13 +6645,19 @@
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz",
|
||||
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz",
|
||||
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
@@ -6504,6 +6707,16 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
|
||||
"integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-buffer": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/to-buffer/-/to-buffer-1.2.1.tgz",
|
||||
@@ -7049,6 +7262,97 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/vitest/-/vitest-4.0.4.tgz",
|
||||
"integrity": "sha512-hV31h0/bGbtmDQc0KqaxsTO1v4ZQeF8ojDFuy4sZhFadwAqqvJA0LDw68QUocctI5EDpFMql/jVWKuPYHIf2Ew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.4",
|
||||
"@vitest/mocker": "4.0.4",
|
||||
"@vitest/pretty-format": "4.0.4",
|
||||
"@vitest/runner": "4.0.4",
|
||||
"@vitest/snapshot": "4.0.4",
|
||||
"@vitest/spy": "4.0.4",
|
||||
"@vitest/utils": "4.0.4",
|
||||
"debug": "^4.4.3",
|
||||
"es-module-lexer": "^1.7.0",
|
||||
"expect-type": "^1.2.2",
|
||||
"magic-string": "^0.30.19",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"std-env": "^3.9.0",
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^0.3.2",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tinyrainbow": "^3.0.3",
|
||||
"vite": "^6.0.0 || ^7.0.0",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"vitest": "vitest.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.0.4",
|
||||
"@vitest/browser-preview": "4.0.4",
|
||||
"@vitest/browser-webdriverio": "4.0.4",
|
||||
"@vitest/ui": "4.0.4",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/debug": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-playwright": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-preview": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-webdriverio": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
"happy-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"jsdom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/vm-browserify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
||||
@@ -7236,6 +7540,23 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/why-is-node-running": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"siginfo": "^2.0.0",
|
||||
"stackback": "0.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"why-is-node-running": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix",
|
||||
"build:lang-parser": "node src/views/editor/extensions/codeblock/lang-parser/build-parser.js"
|
||||
"build:lang-parser": "node src/views/editor/extensions/codeblock/lang-parser/build-parser.js",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.19.0",
|
||||
@@ -85,6 +86,7 @@
|
||||
"unplugin-vue-components": "^30.0.0",
|
||||
"vite": "^7.1.12",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"vitest": "^4.0.4",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.1.1"
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {AsyncManager} from '@/common/utils/asyncManager';
|
||||
import {generateContentHash} from "@/common/utils/hashUtils";
|
||||
import {createTimerManager, type TimerManager} from '@/common/utils/timerUtils';
|
||||
import {EDITOR_CONFIG} from '@/common/constant/editor';
|
||||
import {createHttpClientExtension} from "@/views/editor/extensions/httpclient";
|
||||
|
||||
export interface DocumentStats {
|
||||
lines: number;
|
||||
@@ -154,6 +155,8 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
enableAutoDetection: true
|
||||
});
|
||||
|
||||
const httpExtension = createHttpClientExtension();
|
||||
|
||||
// 再次检查操作有效性
|
||||
if (!operationManager.isOperationValid(operationId, documentId)) {
|
||||
throw new Error('Operation cancelled');
|
||||
@@ -185,7 +188,8 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
statsExtension,
|
||||
contentChangeExtension,
|
||||
codeBlockExtension,
|
||||
...dynamicExtensions
|
||||
...dynamicExtensions,
|
||||
...httpExtension
|
||||
];
|
||||
|
||||
// 创建编辑器状态
|
||||
|
||||
@@ -17,7 +17,8 @@ BlockLanguage {
|
||||
"css" | "xml" | "cpp" | "rs" | "cs" | "rb" | "sh" | "yaml" | "toml" |
|
||||
"go" | "clj" | "ex" | "erl" | "js" | "ts" | "swift" | "kt" | "groovy" |
|
||||
"ps1" | "dart" | "scala" | "math" | "dockerfile" | "lua" | "vue" | "lezer" |
|
||||
"liquid" | "wast" | "sass" | "less" | "angular" | "svelte"
|
||||
"liquid" | "wast" | "sass" | "less" | "angular" | "svelte" |
|
||||
"http"
|
||||
}
|
||||
|
||||
@tokens {
|
||||
|
||||
@@ -23,6 +23,7 @@ import {sassLanguage} from "@codemirror/lang-sass";
|
||||
import {lessLanguage} from "@codemirror/lang-less";
|
||||
import {angularLanguage} from "@codemirror/lang-angular";
|
||||
import { svelteLanguage } from "@replit/codemirror-lang-svelte";
|
||||
import { httpLanguage } from "@/views/editor/extensions/httpclient/language/http-language";
|
||||
|
||||
import {StreamLanguage} from "@codemirror/language";
|
||||
import {ruby} from "@codemirror/legacy-modes/mode/ruby";
|
||||
@@ -224,6 +225,7 @@ export const LANGUAGES: LanguageInfo[] = [
|
||||
filename: "index.svelte"
|
||||
}
|
||||
}),
|
||||
new LanguageInfo("http", "Http", httpLanguage.parser, ["http"]),
|
||||
|
||||
];
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import {LRParser} from "@lezer/lr"
|
||||
import {blockContent} from "./external-tokens.js"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "!jQQOQOOOVOQO'#C`O#uOPO'#C_OOOO'#Cc'#CcQQOQOOOOOO'#Ca'#CaO#zOSO,58zOOOO,58y,58yOOOO-E6a-E6aOOOP1G.f1G.fO$SOSO1G.fOOOP7+$Q7+$Q",
|
||||
stateData: "$X~OXPO~OYTOZTO[TO]TO^TO_TO`TOaTObTOcTOdTOeTOfTOgTOhTOiTOjTOkTOlTOmTOnTOoTOpTOqTOrTOsTOtTOuTOvTOwTOxTOyTOzTO{TO|TO}TO!OTO!PTO!QTO!RTO~OPVO~OUYO!SXO~O!SZO~O",
|
||||
states: "!jQQOQOOOVOQO'#C`O#xOPO'#C_OOOO'#Cc'#CcQQOQOOOOOO'#Ca'#CaO#}OSO,58zOOOO,58y,58yOOOO-E6a-E6aOOOP1G.f1G.fO$VOSO1G.fOOOP7+$Q7+$Q",
|
||||
stateData: "$[~OXPO~OYTOZTO[TO]TO^TO_TO`TOaTObTOcTOdTOeTOfTOgTOhTOiTOjTOkTOlTOmTOnTOoTOpTOqTOrTOsTOtTOuTOvTOwTOxTOyTOzTO{TO|TO}TO!OTO!PTO!QTO!RTO!STO~OPVO~OUYO!TXO~O!TZO~O",
|
||||
goto: "jWPPPX]aPdTROSTQOSRUPQSORWS",
|
||||
nodeNames: "⚠ BlockContent Document Block BlockDelimiter BlockLanguage Auto",
|
||||
maxTerm: 50,
|
||||
maxTerm: 51,
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 1,
|
||||
tokenData: "3g~RdYZ!a}!O!z#T#U#V#V#W$Q#W#X%R#X#Y&t#Z#['_#[#]([#^#_(s#_#`)r#`#a)}#a#b+z#d#e,k#f#g-d#g#h-w#h#i0n#j#k1s#k#l2U#l#m2m#m#n3OR!fP!SQ%&x%&y!iP!lP%&x%&y!oP!rP%&x%&y!uP!zOXP~!}P#T#U#Q~#VOU~~#YP#b#c#]~#`P#Z#[#c~#fP#i#j#i~#lP#`#a#o~#rP#T#U#u~#xP#f#g#{~$QO!Q~~$TR#`#a$^#d#e$i#g#h$t~$aP#^#_$d~$iOl~~$lP#d#e$o~$tOd~~$yPf~#g#h$|~%ROb~~%UQ#T#U%[#c#d%m~%_P#f#g%b~%eP#h#i%h~%mOu~~%pP#V#W%s~%vP#_#`%y~%|P#X#Y&P~&SP#f#g&V~&YP#Y#Z&]~&`P#]#^&c~&fP#`#a&i~&lP#X#Y&o~&tOx~~&wQ#f#g&}#l#m'Y~'QP#`#a'T~'YOn~~'_Om~~'bQ#c#d'h#f#g'm~'mOk~~'pP#c#d's~'vP#c#d'y~'|P#j#k(P~(SP#m#n(V~([Os~~(_P#h#i(b~(eP#a#b(h~(kP#`#a(n~(sO]~~(vQ#T#U(|#g#h)_~)PP#j#k)S~)VP#T#U)Y~)_O`~~)dPo~#c#d)g~)jP#b#c)m~)rOZ~~)uP#h#i)x~)}Or~~*QR#X#Y*Z#]#^+Q#i#j+o~*^Q#g#h*d#n#o*o~*gP#g#h*j~*oO!P~~*rP#X#Y*u~*xP#f#g*{~+QO{~~+TP#e#f+W~+ZP#i#j+^~+aP#]#^+d~+gP#W#X+j~+oO|~~+rP#T#U+u~+zOy~~+}Q#T#U,T#W#X,f~,WP#h#i,Z~,^P#[#],a~,fOw~~,kO_~~,nR#[#],w#g#h-S#m#n-_~,zP#d#e,}~-SOa~~-VP!R!S-Y~-_Ot~~-dO[~~-gQ#U#V-m#g#h-r~-rOg~~-wOe~~-zU#T#U.^#V#W.o#[#]/W#e#f/]#j#k/h#k#l0V~.aP#g#h.d~.gP#g#h.j~.oO!O~~.rP#T#U.u~.xP#`#a.{~/OP#T#U/R~/WOv~~/]Oh~~/`P#`#a/c~/hO^~~/kP#X#Y/n~/qP#`#a/t~/wP#h#i/z~/}P#X#Y0Q~0VO!R~~0YP#]#^0]~0`P#Y#Z0c~0fP#h#i0i~0nOq~~0qR#X#Y0z#c#d1]#g#h1n~0}P#l#m1Q~1TP#h#i1W~1]OY~~1`P#a#b1c~1fP#`#a1i~1nOj~~1sOp~~1vP#i#j1y~1|P#X#Y2P~2UOz~~2XP#T#U2[~2_P#g#h2b~2eP#h#i2h~2mO}~~2pP#a#b2s~2vP#`#a2y~3OOc~~3RP#T#U3U~3XP#a#b3[~3_P#`#a3b~3gOi~",
|
||||
tokenData: "3u~RdYZ!a}!O!z#T#U#V#V#W$Q#W#X%R#X#Y&t#Z#['_#[#]([#^#_)R#_#`*Q#`#a*]#a#b,Y#d#e,y#f#g-r#g#h.V#h#i0|#j#k2R#k#l2d#l#m2{#m#n3^R!fP!TQ%&x%&y!iP!lP%&x%&y!oP!rP%&x%&y!uP!zOXP~!}P#T#U#Q~#VOU~~#YP#b#c#]~#`P#Z#[#c~#fP#i#j#i~#lP#`#a#o~#rP#T#U#u~#xP#f#g#{~$QO!Q~~$TR#`#a$^#d#e$i#g#h$t~$aP#^#_$d~$iOl~~$lP#d#e$o~$tOd~~$yPf~#g#h$|~%ROb~~%UQ#T#U%[#c#d%m~%_P#f#g%b~%eP#h#i%h~%mOu~~%pP#V#W%s~%vP#_#`%y~%|P#X#Y&P~&SP#f#g&V~&YP#Y#Z&]~&`P#]#^&c~&fP#`#a&i~&lP#X#Y&o~&tOx~~&wQ#f#g&}#l#m'Y~'QP#`#a'T~'YOn~~'_Om~~'bQ#c#d'h#f#g'm~'mOk~~'pP#c#d's~'vP#c#d'y~'|P#j#k(P~(SP#m#n(V~([Os~~(_P#h#i(b~(eQ#a#b(k#h#i(v~(nP#`#a(q~(vO]~~(yP#d#e(|~)RO!S~~)UQ#T#U)[#g#h)m~)_P#j#k)b~)eP#T#U)h~)mO`~~)rPo~#c#d)u~)xP#b#c){~*QOZ~~*TP#h#i*W~*]Or~~*`R#X#Y*i#]#^+`#i#j+}~*lQ#g#h*r#n#o*}~*uP#g#h*x~*}O!P~~+QP#X#Y+T~+WP#f#g+Z~+`O{~~+cP#e#f+f~+iP#i#j+l~+oP#]#^+r~+uP#W#X+x~+}O|~~,QP#T#U,T~,YOy~~,]Q#T#U,c#W#X,t~,fP#h#i,i~,lP#[#],o~,tOw~~,yO_~~,|R#[#]-V#g#h-b#m#n-m~-YP#d#e-]~-bOa~~-eP!R!S-h~-mOt~~-rO[~~-uQ#U#V-{#g#h.Q~.QOg~~.VOe~~.YU#T#U.l#V#W.}#[#]/f#e#f/k#j#k/v#k#l0e~.oP#g#h.r~.uP#g#h.x~.}O!O~~/QP#T#U/T~/WP#`#a/Z~/^P#T#U/a~/fOv~~/kOh~~/nP#`#a/q~/vO^~~/yP#X#Y/|~0PP#`#a0S~0VP#h#i0Y~0]P#X#Y0`~0eO!R~~0hP#]#^0k~0nP#Y#Z0q~0tP#h#i0w~0|Oq~~1PR#X#Y1Y#c#d1k#g#h1|~1]P#l#m1`~1cP#h#i1f~1kOY~~1nP#a#b1q~1tP#`#a1w~1|Oj~~2ROp~~2UP#i#j2X~2[P#X#Y2_~2dOz~~2gP#T#U2j~2mP#g#h2p~2sP#h#i2v~2{O}~~3OP#a#b3R~3UP#`#a3X~3^Oc~~3aP#T#U3d~3gP#a#b3j~3mP#`#a3p~3uOi~",
|
||||
tokenizers: [blockContent, 0, 1],
|
||||
topRules: {"Document":[0,2]},
|
||||
tokenPrec: 0
|
||||
|
||||
@@ -27,6 +27,11 @@ export const blockState = StateField.define<Block[]>({
|
||||
* 获取当前活动的块
|
||||
*/
|
||||
export function getActiveNoteBlock(state: EditorState): Block | undefined {
|
||||
// 检查 blockState 字段是否存在
|
||||
if (!state.field(blockState, false)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 找到光标所在的块
|
||||
const range = state.selection.asSingle().ranges[0];
|
||||
return state.field(blockState).find(block =>
|
||||
@@ -38,6 +43,9 @@ export function getActiveNoteBlock(state: EditorState): Block | undefined {
|
||||
* 获取第一个块
|
||||
*/
|
||||
export function getFirstNoteBlock(state: EditorState): Block | undefined {
|
||||
if (!state.field(blockState, false)) {
|
||||
return undefined;
|
||||
}
|
||||
return state.field(blockState)[0];
|
||||
}
|
||||
|
||||
@@ -45,6 +53,9 @@ export function getFirstNoteBlock(state: EditorState): Block | undefined {
|
||||
* 获取最后一个块
|
||||
*/
|
||||
export function getLastNoteBlock(state: EditorState): Block | undefined {
|
||||
if (!state.field(blockState, false)) {
|
||||
return undefined;
|
||||
}
|
||||
const blocks = state.field(blockState);
|
||||
return blocks[blocks.length - 1];
|
||||
}
|
||||
@@ -53,6 +64,9 @@ export function getLastNoteBlock(state: EditorState): Block | undefined {
|
||||
* 根据位置获取块
|
||||
*/
|
||||
export function getNoteBlockFromPos(state: EditorState, pos: number): Block | undefined {
|
||||
if (!state.field(blockState, false)) {
|
||||
return undefined;
|
||||
}
|
||||
return state.field(blockState).find(block =>
|
||||
block.range.from <= pos && block.range.to >= pos
|
||||
);
|
||||
|
||||
@@ -65,6 +65,7 @@ export type SupportedLanguage =
|
||||
| 'less'
|
||||
| 'angular'
|
||||
| 'svelte'
|
||||
| 'http' // HTTP Client
|
||||
|
||||
/**
|
||||
* 创建块的选项
|
||||
|
||||
31
frontend/src/views/editor/extensions/httpclient/index.ts
Normal file
31
frontend/src/views/editor/extensions/httpclient/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* HTTP Client 扩展
|
||||
*/
|
||||
|
||||
import {Extension} from '@codemirror/state';
|
||||
|
||||
import {httpRunButtonGutter, httpRunButtonTheme} from './widgets/run-gutter';
|
||||
|
||||
/**
|
||||
* 创建 HTTP Client 扩展
|
||||
*/
|
||||
export function createHttpClientExtension(): Extension[] {
|
||||
|
||||
const extensions: Extension[] = [];
|
||||
|
||||
// HTTP 语言解析器
|
||||
// extensions.push(httpLanguage);
|
||||
|
||||
// 运行按钮 Gutte
|
||||
extensions.push(httpRunButtonGutter);
|
||||
extensions.push(httpRunButtonTheme);
|
||||
|
||||
|
||||
// TODO: 后续阶段添加
|
||||
// - 自动补全(可选)
|
||||
// - 变量支持(可选)
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* HTTP Grammar Parser Builder
|
||||
* 编译 Lezer grammar 文件为 JavaScript parser
|
||||
* 使用命令行方式编译
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
console.log('🚀 开始编译 HTTP grammar parser...');
|
||||
|
||||
try {
|
||||
// 检查语法文件是否存在
|
||||
const grammarFile = path.join(__dirname, 'http.grammar');
|
||||
if (!fs.existsSync(grammarFile)) {
|
||||
throw new Error('语法文件 http.grammar 未找到');
|
||||
}
|
||||
|
||||
console.log('📄 语法文件:', grammarFile);
|
||||
|
||||
// 运行 lezer-generator
|
||||
console.log('⚙️ 编译 parser...');
|
||||
execSync('npx lezer-generator http.grammar -o http.grammar.js', {
|
||||
cwd: __dirname,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
// 检查生成的文件
|
||||
const parserFile = path.join(__dirname, 'http.grammar.js');
|
||||
const termsFile = path.join(__dirname, 'http.grammar.terms.js');
|
||||
|
||||
if (fs.existsSync(parserFile) && fs.existsSync(termsFile)) {
|
||||
console.log('✅ Parser 文件成功生成!');
|
||||
console.log('📦 生成的文件:');
|
||||
console.log(' - http.grammar.js');
|
||||
console.log(' - http.grammar.terms.js');
|
||||
} else {
|
||||
throw new Error('Parser 生成失败');
|
||||
}
|
||||
|
||||
console.log('🎉 编译成功!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 编译失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { styleTags, tags as t } from '@lezer/highlight';
|
||||
|
||||
/**
|
||||
* HTTP Client 语法高亮配置
|
||||
*/
|
||||
export const httpHighlighting = styleTags({
|
||||
// 注释
|
||||
LineComment: t.lineComment,
|
||||
|
||||
// HTTP 方法关键字
|
||||
"GET POST PUT DELETE PATCH HEAD OPTIONS": t.keyword,
|
||||
|
||||
// 关键字
|
||||
"HEADER BODY VARIABLES RESPONSE": t.keyword,
|
||||
|
||||
// Body 类型
|
||||
"TEXT JSON XML FORM URLENCODED GRAPHQL BINARY": t.keyword,
|
||||
|
||||
// 变量名
|
||||
VariableName: t.variableName,
|
||||
VariableValue: t.string,
|
||||
|
||||
// URL 和版本
|
||||
Url: t.url,
|
||||
UrlText: t.url,
|
||||
HttpVersion: t.literal,
|
||||
|
||||
// Header
|
||||
HeaderName: t.propertyName,
|
||||
HeaderValue: t.string,
|
||||
|
||||
// Body 内容
|
||||
BodyContent: t.content,
|
||||
|
||||
// Variables 内容
|
||||
VariablesContent: t.content,
|
||||
|
||||
// 响应
|
||||
StatusCode: t.number,
|
||||
StatusText: t.string,
|
||||
Duration: t.literal,
|
||||
Size: t.literal,
|
||||
Timestamp: t.literal,
|
||||
|
||||
// 文件引用
|
||||
FilePath: t.string,
|
||||
|
||||
// 模板表达式
|
||||
"{{ }}": t.special(t.brace),
|
||||
"TemplateExpression/VariableName": t.variableName,
|
||||
|
||||
// 成员访问
|
||||
"MemberExpression/VariableName": t.variableName,
|
||||
"MemberExpression/PropertyName": t.propertyName,
|
||||
PropertyName: t.propertyName,
|
||||
|
||||
// 函数调用
|
||||
FunctionName: t.function(t.variableName),
|
||||
|
||||
// 基础类型
|
||||
Number: t.number,
|
||||
String: t.string,
|
||||
|
||||
// 符号
|
||||
": Spread": t.punctuation,
|
||||
"( )": t.paren,
|
||||
"[ ]": t.squareBracket,
|
||||
"{ }": t.brace,
|
||||
".": t.derefOperator,
|
||||
", ;": t.separator,
|
||||
"@": t.meta,
|
||||
"$": t.meta,
|
||||
"=": t.definitionOperator,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { LRLanguage, LanguageSupport, foldNodeProp, foldInside, indentNodeProp } from '@codemirror/language';
|
||||
import { parser } from './http.grammar.js';
|
||||
import { httpHighlighting } from './http-highlight';
|
||||
|
||||
/**
|
||||
* HTTP Client 语言定义
|
||||
*/
|
||||
|
||||
// 配置折叠规则和高亮
|
||||
const httpParserWithMetadata = parser.configure({
|
||||
props: [
|
||||
// 应用语法高亮
|
||||
httpHighlighting,
|
||||
|
||||
// 折叠规则:允许折叠多行 Body、Variables、Headers 等
|
||||
foldNodeProp.add({
|
||||
BodyStatement: foldInside,
|
||||
VariablesStatement: foldInside,
|
||||
Document: foldInside,
|
||||
}),
|
||||
|
||||
// 缩进规则
|
||||
indentNodeProp.add({
|
||||
BodyStatement: () => 2,
|
||||
HeaderStatement: () => 0,
|
||||
VariableDeclaration: () => 0,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
// 创建 LR 语言实例
|
||||
export const httpLanguage = LRLanguage.define({
|
||||
parser: httpParserWithMetadata,
|
||||
languageData: {
|
||||
// 注释配置
|
||||
commentTokens: { line: '#' },
|
||||
|
||||
// 自动闭合括号
|
||||
closeBrackets: { brackets: ['(', '[', '{', '"', "'"] },
|
||||
|
||||
// 单词字符定义
|
||||
wordChars: '-_',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* HTTP Client 语言支持
|
||||
* 包含语法高亮、折叠、缩进等完整功能
|
||||
*/
|
||||
export function http() {
|
||||
return new LanguageSupport(httpLanguage, [
|
||||
httpLanguage.data.of({
|
||||
autocomplete: httpCompletion,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP Client 自动补全
|
||||
*/
|
||||
function httpCompletion(context: any) {
|
||||
const word = context.matchBefore(/\w*/);
|
||||
if (!word || (word.from === word.to && !context.explicit)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
from: word.from,
|
||||
options: [
|
||||
// HTTP 方法
|
||||
{ label: 'GET', type: 'keyword', detail: 'HTTP Method' },
|
||||
{ label: 'POST', type: 'keyword', detail: 'HTTP Method' },
|
||||
{ label: 'PUT', type: 'keyword', detail: 'HTTP Method' },
|
||||
{ label: 'DELETE', type: 'keyword', detail: 'HTTP Method' },
|
||||
{ label: 'PATCH', type: 'keyword', detail: 'HTTP Method' },
|
||||
{ label: 'HEAD', type: 'keyword', detail: 'HTTP Method' },
|
||||
{ label: 'OPTIONS', type: 'keyword', detail: 'HTTP Method' },
|
||||
|
||||
// 关键字
|
||||
{ label: 'HEADER', type: 'keyword', detail: 'Header Statement' },
|
||||
{ label: 'BODY', type: 'keyword', detail: 'Body Statement' },
|
||||
{ label: 'VARIABLES', type: 'keyword', detail: 'Variables Statement' },
|
||||
|
||||
// Body 类型
|
||||
{ label: 'TEXT', type: 'keyword', detail: 'Body Type' },
|
||||
{ label: 'JSON', type: 'keyword', detail: 'Body Type' },
|
||||
{ label: 'XML', type: 'keyword', detail: 'Body Type' },
|
||||
{ label: 'FORM', type: 'keyword', detail: 'Body Type' },
|
||||
{ label: 'URLENCODED', type: 'keyword', detail: 'Body Type' },
|
||||
{ label: 'GRAPHQL', type: 'keyword', detail: 'Body Type' },
|
||||
{ label: 'BINARY', type: 'keyword', detail: 'Body Type' },
|
||||
|
||||
// HTTP 版本
|
||||
{ label: 'HTTP/1.0', type: 'constant', detail: 'HTTP Version' },
|
||||
{ label: 'HTTP/1.1', type: 'constant', detail: 'HTTP Version' },
|
||||
{ label: 'HTTP/2.0', type: 'constant', detail: 'HTTP Version' },
|
||||
|
||||
// 常用 Headers
|
||||
{ label: 'Content-Type', type: 'property', detail: 'Header Name' },
|
||||
{ label: 'Authorization', type: 'property', detail: 'Header Name' },
|
||||
{ label: 'Accept', type: 'property', detail: 'Header Name' },
|
||||
{ label: 'User-Agent', type: 'property', detail: 'Header Name' },
|
||||
{ label: 'Cookie', type: 'property', detail: 'Header Name' },
|
||||
|
||||
// 常用 Content-Type
|
||||
{ label: 'application/json', type: 'constant', detail: 'Content Type' },
|
||||
{ label: 'application/xml', type: 'constant', detail: 'Content Type' },
|
||||
{ label: 'text/html', type: 'constant', detail: 'Content Type' },
|
||||
{ label: 'text/plain', type: 'constant', detail: 'Content Type' },
|
||||
{ label: 'multipart/form-data', type: 'constant', detail: 'Content Type' },
|
||||
{ label: 'application/x-www-form-urlencoded', type: 'constant', detail: 'Content Type' },
|
||||
|
||||
// 特殊标记
|
||||
{ label: '@timestamp', type: 'keyword', detail: 'Timestamp' },
|
||||
{ label: '@file', type: 'keyword', detail: 'File Reference' },
|
||||
|
||||
// 内置函数
|
||||
{ label: '$timestamp()', type: 'function', detail: 'Current Timestamp' },
|
||||
{ label: '$uuid()', type: 'function', detail: 'Generate UUID' },
|
||||
{ label: '$randomInt()', type: 'function', detail: 'Random Integer' },
|
||||
{ label: '$hash()', type: 'function', detail: 'Hash Function' },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出语言定义和高亮配置
|
||||
*/
|
||||
export { httpHighlighting };
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
@precedence {
|
||||
member,
|
||||
call
|
||||
}
|
||||
|
||||
@top Document { statement* }
|
||||
|
||||
statement {
|
||||
VariableDeclaration |
|
||||
ResponseLine |
|
||||
RequestLine |
|
||||
HeaderStatement |
|
||||
BodyStatement |
|
||||
VariablesStatement
|
||||
}
|
||||
|
||||
// ==================== 变量定义 ====================
|
||||
|
||||
VariableDeclaration {
|
||||
"@" VariableName "=" VariableValue ";"
|
||||
}
|
||||
|
||||
VariableName { word }
|
||||
|
||||
VariableValue[isolate] { anyContent }
|
||||
|
||||
// ==================== 请求行 ====================
|
||||
|
||||
RequestLine {
|
||||
Method Url HttpVersion? ";"
|
||||
}
|
||||
|
||||
Method {
|
||||
@specialize[@name="GET"]<word, "GET"> |
|
||||
@specialize[@name="POST"]<word, "POST"> |
|
||||
@specialize[@name="PUT"]<word, "PUT"> |
|
||||
@specialize[@name="DELETE"]<word, "DELETE"> |
|
||||
@specialize[@name="PATCH"]<word, "PATCH"> |
|
||||
@specialize[@name="HEAD"]<word, "HEAD"> |
|
||||
@specialize[@name="OPTIONS"]<word, "OPTIONS"> |
|
||||
@specialize[@name="CONNECT"]<word, "CONNECT"> |
|
||||
@specialize[@name="TRACE"]<word, "TRACE">
|
||||
}
|
||||
|
||||
Url { urlPart+ }
|
||||
|
||||
urlPart { urlContent | TemplateExpression }
|
||||
|
||||
HttpVersion { httpVersionToken }
|
||||
|
||||
// ==================== Header 语句 ====================
|
||||
|
||||
HeaderStatement {
|
||||
HeaderKeyword HeaderName colon HeaderValue ";"
|
||||
}
|
||||
|
||||
colon { ":" }
|
||||
|
||||
HeaderKeyword { @specialize[@name="HEADER"]<word, "HEADER"> }
|
||||
|
||||
HeaderName { word }
|
||||
|
||||
HeaderValue { headerValuePart* }
|
||||
|
||||
headerValuePart { headerValueContent | TemplateExpression }
|
||||
|
||||
// ==================== Body 语句 ====================
|
||||
|
||||
BodyStatement {
|
||||
BodyKeyword BodyType BodyContent ";"
|
||||
}
|
||||
|
||||
BodyKeyword { @specialize[@name="BODY"]<word, "BODY"> }
|
||||
|
||||
BodyType {
|
||||
@specialize[@name="JSON"]<word, "JSON"> |
|
||||
@specialize[@name="FORM"]<word, "FORM"> |
|
||||
@specialize[@name="URLENCODED"]<word, "URLENCODED"> |
|
||||
@specialize[@name="GRAPHQL"]<word, "GRAPHQL"> |
|
||||
@specialize[@name="XML"]<word, "XML"> |
|
||||
@specialize[@name="TEXT"]<word, "TEXT"> |
|
||||
@specialize[@name="BINARY"]<word, "BINARY"> |
|
||||
@specialize[@name="MULTIPART"]<word, "MULTIPART">
|
||||
}
|
||||
|
||||
BodyContent { bodyContentPart* }
|
||||
|
||||
bodyContentPart { bodyText | TemplateExpression | FileReference }
|
||||
|
||||
FileReference {
|
||||
"@file" FilePath
|
||||
}
|
||||
|
||||
FilePath { filePathContent }
|
||||
|
||||
// ==================== Variables 语句 ====================
|
||||
|
||||
VariablesStatement {
|
||||
VariablesKeyword VariablesContent ";"
|
||||
}
|
||||
|
||||
VariablesKeyword { @specialize[@name="VARIABLES"]<word, "VARIABLES"> }
|
||||
|
||||
VariablesContent[isolate] { variablesContent }
|
||||
|
||||
// ==================== 响应 ====================
|
||||
|
||||
// 响应行 - 固定格式:RESPONSE <状态码> <状态文本> <大小> <时间戳>;
|
||||
ResponseLine {
|
||||
ResponseKeyword StatusCode StatusText Size Timestamp ";"
|
||||
}
|
||||
|
||||
ResponseKeyword { @specialize[@name="RESPONSE"]<word, "RESPONSE"> }
|
||||
|
||||
StatusCode { Number }
|
||||
|
||||
StatusText { word+ }
|
||||
|
||||
Size { Number sizeUnit }
|
||||
|
||||
// 时间戳格式:YYYY-MM-DD HH:MM:SS 或 ISO8601 格式
|
||||
Timestamp { timestampContent }
|
||||
|
||||
// ==================== 模板表达式 ====================
|
||||
|
||||
TemplateExpression {
|
||||
"{{" templateContent "}}"
|
||||
}
|
||||
|
||||
templateContent {
|
||||
VariableName |
|
||||
MemberExpression |
|
||||
FunctionCall
|
||||
}
|
||||
|
||||
MemberExpression {
|
||||
VariableName !member ("." PropertyName)+
|
||||
}
|
||||
|
||||
PropertyName { word }
|
||||
|
||||
FunctionCall {
|
||||
"$" FunctionName !call "(" argumentList? ")"
|
||||
}
|
||||
|
||||
FunctionName { word }
|
||||
|
||||
argumentList {
|
||||
argument ("," argument)*
|
||||
}
|
||||
|
||||
argument { Number | String | word }
|
||||
|
||||
// ==================== Tokens ====================
|
||||
|
||||
@skip { spaces | newline | LineComment }
|
||||
|
||||
@tokens {
|
||||
// 空白字符
|
||||
spaces[@export] { $[ \t]+ }
|
||||
|
||||
newline[@export] { $[\r\n] }
|
||||
|
||||
// 注释
|
||||
LineComment[@export,isolate] { "#" ![\n]* }
|
||||
|
||||
// 标识符
|
||||
identifierChar { @asciiLetter | $[_$] }
|
||||
|
||||
word { identifierChar (identifierChar | @digit | $[-])* }
|
||||
|
||||
// 数字
|
||||
Number {
|
||||
@digit+ ("." @digit+)?
|
||||
}
|
||||
|
||||
// 字符串
|
||||
String {
|
||||
'"' stringContentDouble* '"' |
|
||||
"'" stringContentSingle* "'"
|
||||
}
|
||||
|
||||
stringContentDouble { ![\\\n"]+ | "\\" _ }
|
||||
|
||||
stringContentSingle { ![\\\n']+ | "\\" _ }
|
||||
|
||||
// URL 内容 - 排除空格、换行、特殊前缀字符
|
||||
// 允许 # (用于锚点),但不允许以 # 开头(避免和注释冲突)
|
||||
urlContent { ![ \t\n\r@{};]+ }
|
||||
|
||||
// HTTP 版本
|
||||
httpVersionToken { "HTTP/" @digit+ "." @digit+ }
|
||||
|
||||
// Header 值内容 - 排除所有 skip tokens 和特殊字符
|
||||
headerValueContent { ![ \t\n\r#{};]+ }
|
||||
|
||||
// Body 文本内容 - 排除空格、换行、分号
|
||||
// 允许大括号、#、@、$ 等,因为JSON/XML/GraphQL/email等需要它们
|
||||
// {{ 和 @file 是独立token,有更高优先级
|
||||
bodyText { ![ \t\n\r;]+ }
|
||||
|
||||
// 任意内容(直到分号) - 只排除换行和分号
|
||||
// 允许空格、#、数字等所有字符
|
||||
anyContent { ![\n\r;]+ }
|
||||
|
||||
// 文件路径内容 - 排除空格、换行、分号
|
||||
filePathContent { ![ \t\n\r;]+ }
|
||||
|
||||
// Variables 内容 - 只排除分号,允许所有字符包括换行
|
||||
variablesContent { ![;]+ }
|
||||
|
||||
// 时间戳内容 - 日期时间格式,包含日期时间字符
|
||||
timestampContent { timestampChar+ }
|
||||
|
||||
timestampChar { @digit | $[-:TZ.+] }
|
||||
|
||||
// Duration 单位
|
||||
durationUnit { "ms" | "s" | "m" }
|
||||
|
||||
// Size 单位
|
||||
sizeUnit { "B" | "KB" | "MB" | "GB" }
|
||||
|
||||
// 优先级声明
|
||||
@precedence { urlContent, LineComment }
|
||||
|
||||
@precedence { "{{", bodyText }
|
||||
|
||||
@precedence { "@file", bodyText }
|
||||
|
||||
@precedence { "$", bodyText }
|
||||
|
||||
@precedence { bodyText, LineComment }
|
||||
|
||||
@precedence { anyContent, LineComment }
|
||||
|
||||
@precedence { anyContent, spaces }
|
||||
|
||||
@precedence { filePathContent, LineComment }
|
||||
|
||||
@precedence { variablesContent, newline }
|
||||
|
||||
@precedence { variablesContent, LineComment }
|
||||
|
||||
@precedence { variablesContent, spaces }
|
||||
|
||||
@precedence { "$", word }
|
||||
|
||||
@precedence { spaces, newline, word }
|
||||
|
||||
@precedence { httpVersionToken, urlContent, word }
|
||||
|
||||
@precedence { "{{", bodyText }
|
||||
|
||||
@precedence { word, bodyText }
|
||||
|
||||
@precedence { Number, word }
|
||||
|
||||
// 符号
|
||||
"(" ")" "[" "]" "{" "}"
|
||||
|
||||
"." "," ":" "=" "@" "$" ";" "{{" "}}"
|
||||
|
||||
// 组合 tokens
|
||||
"@file"
|
||||
}
|
||||
|
||||
@detectDelim
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,60 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const
|
||||
spaces = 78,
|
||||
newline = 79,
|
||||
LineComment = 1,
|
||||
Document = 2,
|
||||
VariableDeclaration = 4,
|
||||
VariableName = 6,
|
||||
VariableValue = 8,
|
||||
ResponseLine = 9,
|
||||
ResponseKeyword = 10,
|
||||
RESPONSE = 11,
|
||||
StatusCode = 12,
|
||||
Number = 13,
|
||||
StatusText = 14,
|
||||
Size = 15,
|
||||
Timestamp = 16,
|
||||
RequestLine = 17,
|
||||
Method = 18,
|
||||
GET = 19,
|
||||
POST = 20,
|
||||
PUT = 21,
|
||||
DELETE = 22,
|
||||
PATCH = 23,
|
||||
HEAD = 24,
|
||||
OPTIONS = 25,
|
||||
CONNECT = 26,
|
||||
TRACE = 27,
|
||||
Url = 28,
|
||||
TemplateExpression = 31,
|
||||
MemberExpression = 32,
|
||||
PropertyName = 34,
|
||||
FunctionCall = 37,
|
||||
FunctionName = 38,
|
||||
String = 40,
|
||||
HttpVersion = 42,
|
||||
HeaderStatement = 43,
|
||||
HeaderKeyword = 44,
|
||||
HEADER = 45,
|
||||
HeaderName = 46,
|
||||
HeaderValue = 48,
|
||||
BodyStatement = 49,
|
||||
BodyKeyword = 50,
|
||||
BODY = 51,
|
||||
BodyType = 52,
|
||||
JSON = 53,
|
||||
FORM = 54,
|
||||
URLENCODED = 55,
|
||||
GRAPHQL = 56,
|
||||
XML = 57,
|
||||
TEXT = 58,
|
||||
BINARY = 59,
|
||||
MULTIPART = 60,
|
||||
BodyContent = 61,
|
||||
FileReference = 62,
|
||||
FilePath = 64,
|
||||
VariablesStatement = 65,
|
||||
VariablesKeyword = 66,
|
||||
VARIABLES = 67,
|
||||
VariablesContent = 68
|
||||
@@ -0,0 +1,3 @@
|
||||
export { http, httpLanguage, httpHighlighting } from './http-language';
|
||||
export { parser } from './http.grammar.js';
|
||||
|
||||
1
frontend/src/views/editor/extensions/httpclient/types.ts
Normal file
1
frontend/src/views/editor/extensions/httpclient/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import { EditorView, GutterMarker, gutter } from '@codemirror/view';
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import { getNoteBlockFromPos } from '../../codeblock/state';
|
||||
import type { SyntaxNode } from '@lezer/common';
|
||||
|
||||
// ==================== 常量定义 ====================
|
||||
|
||||
/** 支持的 HTTP 方法(小写) - 使用 Set 以提高查找性能 */
|
||||
const HTTP_METHODS = new Set(['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'connect', 'trace']);
|
||||
|
||||
/** 匹配 ### Request 标记的正则表达式 */
|
||||
const REQUEST_MARKER_REGEX = /^###\s+Request(?:\s|$)/i;
|
||||
|
||||
/** 匹配 ### Response 标记的正则表达式 */
|
||||
const RESPONSE_MARKER_REGEX = /^###\s+Response/i;
|
||||
|
||||
/** 匹配 HTTP 方法的正则表达式 */
|
||||
const HTTP_METHOD_REGEX = /^\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE)\s+/i;
|
||||
|
||||
/** HTTP 方法在行首的最大偏移位置(字符数) */
|
||||
const MAX_METHOD_POSITION_OFFSET = 20;
|
||||
|
||||
/** 向上查找 ### Request 标记的最大行数 */
|
||||
const MAX_REQUEST_MARKER_DISTANCE = 10;
|
||||
|
||||
// ==================== 运行按钮 Marker ====================
|
||||
|
||||
/**
|
||||
* 运行按钮 Gutter Marker
|
||||
*/
|
||||
class RunButtonMarker extends GutterMarker {
|
||||
constructor(private readonly linePosition: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
toDOM(view: EditorView) {
|
||||
const button = document.createElement('button');
|
||||
button.className = 'cm-http-run-button';
|
||||
button.innerHTML = '▶';
|
||||
button.title = 'Run HTTP Request';
|
||||
button.setAttribute('aria-label', 'Run HTTP Request');
|
||||
|
||||
button.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.executeRequest(view);
|
||||
};
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async executeRequest(view: EditorView) {
|
||||
console.log(`\n============ 执行 HTTP 请求 ============`);
|
||||
console.log(`位置: ${this.linePosition}`);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用语法树检查一行是否是 HTTP 请求行(更可靠)
|
||||
* 必须符合规则:前面有 ### Request,然后才是 GET/POST 等请求行
|
||||
*/
|
||||
function isRequestLineInSyntaxTree(view: EditorView, lineFrom: number, lineTo: number): boolean {
|
||||
const tree = syntaxTree(view.state);
|
||||
let hasHttpMethod = false;
|
||||
|
||||
// 遍历该行的语法树节点
|
||||
tree.iterate({
|
||||
from: lineFrom,
|
||||
to: lineTo,
|
||||
enter: (node: SyntaxNode) => {
|
||||
// HTTP 解析器将 HTTP 方法(GET、POST 等)标记为 "keyword"
|
||||
// 并且该节点应该在行首附近
|
||||
if (node.name === 'keyword' &&
|
||||
node.from >= lineFrom &&
|
||||
node.from < lineFrom + MAX_METHOD_POSITION_OFFSET) {
|
||||
const text = view.state.sliceDoc(node.from, node.to);
|
||||
if (HTTP_METHODS.has(text.toLowerCase())) {
|
||||
// 检查前面是否有 ### Request 标记
|
||||
if (hasPrecedingRequestMarker(view, lineFrom)) {
|
||||
hasHttpMethod = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return hasHttpMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查前面是否有 ### Request 标记
|
||||
* 只要包含 "### Request",后面可以跟任何描述文字
|
||||
*/
|
||||
function hasPrecedingRequestMarker(view: EditorView, lineFrom: number): boolean {
|
||||
const currentLineNum = view.state.doc.lineAt(lineFrom).number;
|
||||
|
||||
// 向上查找前面的几行(最多往上找指定行数)
|
||||
for (let i = currentLineNum - 1;
|
||||
i >= Math.max(1, currentLineNum - MAX_REQUEST_MARKER_DISTANCE);
|
||||
i--) {
|
||||
const line = view.state.doc.line(i);
|
||||
const lineText = view.state.sliceDoc(line.from, line.to).trim();
|
||||
|
||||
if (REQUEST_MARKER_REGEX.test(lineText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果遇到 ### Response,停止查找
|
||||
if (RESPONSE_MARKER_REGEX.test(lineText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果是空行,继续往上找
|
||||
if (lineText === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果遇到另一个请求方法,停止查找
|
||||
if (HTTP_METHOD_REGEX.test(lineText)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查位置是否在 HTTP 块内
|
||||
*/
|
||||
function isInHttpBlock(view: EditorView, pos: number): boolean {
|
||||
try {
|
||||
const block = getNoteBlockFromPos(view.state, pos);
|
||||
return block?.language.name === 'http' || block?.language.name === 'rest';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建运行按钮 Gutter
|
||||
*/
|
||||
export const httpRunButtonGutter = gutter({
|
||||
class: 'cm-http-gutter',
|
||||
|
||||
// 为每一行决定是否显示 marker
|
||||
lineMarker(view, line) {
|
||||
const linePos = line.from;
|
||||
|
||||
// 第一步:检查是否在 HTTP 块内
|
||||
if (!isInHttpBlock(view, linePos)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 第二步:使用语法树检查是否是请求行
|
||||
if (!isRequestLineInSyntaxTree(view, line.from, line.to)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建运行按钮
|
||||
return new RunButtonMarker(linePos);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export const httpRunButtonTheme = EditorView.baseTheme({
|
||||
// 运行按钮样式
|
||||
'.cm-http-run-button': {
|
||||
// width: '18px',
|
||||
// height: '18px',
|
||||
border: 'none',
|
||||
borderRadius: '2px',
|
||||
backgroundColor: 'transparent',
|
||||
color: '#4CAF50', // 绿色三角
|
||||
// fontSize: '13px',
|
||||
// lineHeight: '16px',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '0',
|
||||
transition: 'color 0.15s ease',
|
||||
},
|
||||
|
||||
// 悬停效果
|
||||
'.cm-http-run-button:hover': {
|
||||
color: '#45a049', // 深绿色
|
||||
// backgroundColor: 'rgba(76, 175, 80, 0.1)', // 淡绿色背景
|
||||
},
|
||||
|
||||
// 激活效果
|
||||
'.cm-http-run-button:active': {
|
||||
color: '#3d8b40',
|
||||
// backgroundColor: 'rgba(76, 175, 80, 0.2)',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user