Add variable highlighting widgets

This commit is contained in:
Gregory Schier
2023-02-26 15:06:14 -08:00
parent 640d2a1bb4
commit 2c0a891838
11 changed files with 403 additions and 68 deletions

146
package-lock.json generated
View File

@@ -12,7 +12,11 @@
"@codemirror/lang-html": "^6.4.2",
"@codemirror/lang-javascript": "^6.1.4",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.6.0",
"@codemirror/search": "^6.2.3",
"@lezer/generator": "^1.2.2",
"@lezer/highlight": "^1.1.3",
"@lezer/lr": "^1.3.3",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-icons": "^1.2.0",
"@radix-ui/react-popover": "1.0.3",
@@ -21,6 +25,7 @@
"classnames": "^2.3.2",
"codemirror": "^6.0.1",
"framer-motion": "^9.0.4",
"parse-json": "^6.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0",
@@ -29,6 +34,7 @@
"devDependencies": {
"@tauri-apps/cli": "^1.2.2",
"@types/node": "^18.7.10",
"@types/parse-json": "^4.0.0",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.52.0",
@@ -67,7 +73,6 @@
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.18.6"
},
@@ -272,7 +277,6 @@
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -304,7 +308,6 @@
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.18.6",
"chalk": "^2.0.0",
@@ -1067,6 +1070,18 @@
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/generator": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.2.2.tgz",
"integrity": "sha512-O//eH9jTPM1GnbZruuD23xU68Pkuragonn1DEIom4Kt/eJN/QFt7Vzvp1YjV/XBmoUKC+2ySPgrA5fMF9FMM2g==",
"dependencies": {
"@lezer/common": "^1.0.2",
"@lezer/lr": "^1.3.0"
},
"bin": {
"lezer-generator": "dist/lezer-generator.cjs"
}
},
"node_modules/@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
@@ -2043,6 +2058,12 @@
"integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==",
"dev": true
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -2457,7 +2478,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
@@ -2786,7 +2806,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -2872,7 +2891,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@@ -2880,8 +2898,7 @@
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/concat-map": {
"version": "0.0.1",
@@ -3209,6 +3226,14 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-abstract": {
"version": "1.21.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz",
@@ -3366,7 +3391,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
@@ -4275,7 +4299,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
@@ -4438,6 +4461,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
},
"node_modules/is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@@ -4772,6 +4800,11 @@
"node": ">=4"
}
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -4846,6 +4879,14 @@
"node": ">=10"
}
},
"node_modules/lines-and-columns": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz",
"integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -5200,6 +5241,23 @@
"node": ">=6"
}
},
"node_modules/parse-json": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-6.0.2.tgz",
"integrity": "sha512-SA5aMiaIjXkAiBrW/yPgLgQAQg42f7K3ACO+2l/zOvtQBwX58DMUsFJXelW2fx3yMBmWOVkR6j1MGsdSbCA4UA==",
"dependencies": {
"@babel/code-frame": "^7.16.0",
"error-ex": "^1.3.2",
"json-parse-even-better-errors": "^2.3.1",
"lines-and-columns": "^2.0.2"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -6035,7 +6093,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -6672,7 +6729,6 @@
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
"dev": true,
"requires": {
"@babel/highlight": "^7.18.6"
}
@@ -6826,8 +6882,7 @@
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
},
"@babel/helper-validator-option": {
"version": "7.18.6",
@@ -6850,7 +6905,6 @@
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.18.6",
"chalk": "^2.0.0",
@@ -7343,6 +7397,15 @@
"@lezer/lr": "^1.0.0"
}
},
"@lezer/generator": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.2.2.tgz",
"integrity": "sha512-O//eH9jTPM1GnbZruuD23xU68Pkuragonn1DEIom4Kt/eJN/QFt7Vzvp1YjV/XBmoUKC+2ySPgrA5fMF9FMM2g==",
"requires": {
"@lezer/common": "^1.0.2",
"@lezer/lr": "^1.3.0"
}
},
"@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
@@ -8012,6 +8075,12 @@
"integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==",
"dev": true
},
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
"@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -8292,7 +8361,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
@@ -8515,7 +8583,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -8583,7 +8650,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
@@ -8591,8 +8657,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"concat-map": {
"version": "0.0.1",
@@ -8839,6 +8904,14 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"requires": {
"is-arrayish": "^0.2.1"
}
},
"es-abstract": {
"version": "1.21.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz",
@@ -8967,8 +9040,7 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
},
"eslint": {
"version": "8.34.0",
@@ -9652,8 +9724,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
},
"has-property-descriptors": {
"version": "1.0.0",
@@ -9768,6 +9839,11 @@
"is-typed-array": "^1.1.10"
}
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
},
"is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@@ -9993,6 +10069,11 @@
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -10052,6 +10133,11 @@
"integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
"dev": true
},
"lines-and-columns": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz",
"integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w=="
},
"locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -10310,6 +10396,17 @@
"callsites": "^3.0.0"
}
},
"parse-json": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-6.0.2.tgz",
"integrity": "sha512-SA5aMiaIjXkAiBrW/yPgLgQAQg42f7K3ACO+2l/zOvtQBwX58DMUsFJXelW2fx3yMBmWOVkR6j1MGsdSbCA4UA==",
"requires": {
"@babel/code-frame": "^7.16.0",
"error-ex": "^1.3.2",
"json-parse-even-better-errors": "^2.3.1",
"lines-and-columns": "^2.0.2"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -10867,7 +10964,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}

View File

@@ -16,7 +16,11 @@
"@codemirror/lang-html": "^6.4.2",
"@codemirror/lang-javascript": "^6.1.4",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.6.0",
"@codemirror/search": "^6.2.3",
"@lezer/generator": "^1.2.2",
"@lezer/highlight": "^1.1.3",
"@lezer/lr": "^1.3.3",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-icons": "^1.2.0",
"@radix-ui/react-popover": "1.0.3",
@@ -25,6 +29,7 @@
"classnames": "^2.3.2",
"codemirror": "^6.0.1",
"framer-motion": "^9.0.4",
"parse-json": "^6.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0",
@@ -33,20 +38,21 @@
"devDependencies": {
"@tauri-apps/cli": "^1.2.2",
"@types/node": "^18.7.10",
"@types/parse-json": "^4.0.0",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.52.0",
"@vitejs/plugin-react": "^3.0.0",
"autoprefixer": "^10.4.13",
"concurrently": "^7.6.0",
"eslint": "^8.34.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.2",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.52.0",
"autoprefixer": "^10.4.13",
"prettier": "^2.8.4",
"concurrently": "^7.6.0",
"postcss": "^8.4.21",
"prettier": "^2.8.4",
"tailwindcss": "^3.2.7",
"typescript": "^4.6.4",
"vite": "^4.0.0",

View File

@@ -54,6 +54,7 @@ function App() {
/>
<Editor
key={request.id}
useTemplating
defaultValue={request.body}
contentType="application/json"
onChange={(body) => updateRequest.mutate({ body })}

View File

@@ -11,10 +11,22 @@
top: 0;
bottom: 0;
font-size: 0.9rem;
font-family: monospace;
}
.cm-editor .cm-tooltip {
color: gray;
}
.cm-editor .placeholder-widget {
background-color: hsl(var(--color-blue-400));
padding: 0.05em 0.3em;
border-radius: 0.2em;
color: white;
cursor: pointer;
}
.cm-editor .cm-scroller {
border: 1px solid hsl(var(--color-gray-50));
border-radius: var(--border-radius-lg);
background-color: hsl(var(--color-gray-50));
}
@@ -24,7 +36,7 @@
}
.cm-editor.cm-focused .cm-scroller {
border-color: hsl(var(--color-blue-400)/0.4);
box-shadow: 0 0 0 1px hsl(var(--color-blue-400)/0.4);
}
.cm-editor .cm-line {
@@ -77,10 +89,6 @@
color: hsl(var(--color-gray-300));
}
.cm-editor .cm-content {
padding-top: 0.4rem;
}
.cm-editor .cm-foldPlaceholder {
background-color: hsl(var(--color-gray-100));
border: 1px solid hsl(var(--color-gray-200));
@@ -102,7 +110,7 @@
}
.cm-editor .cm-cursor {
border-left: 2px solid red;
border-left: 2px solid hsl(var(--color-gray-900));
}
.cm-editor .cm-selectionBackground {
@@ -112,3 +120,16 @@
.cm-editor.cm-focused .cm-selectionBackground {
background-color: hsl(var(--color-gray-200));
}
/* --> Add padding to container. For some reason, using padding on both adds an extra
* 1px offset so we need to use a combination of padding and margin.
*/
.cm-editor .cm-gutters {
padding-top: 0.2em;
}
.cm-editor .cm-content {
margin-top: 0.2em;
}
/* <-- */

View File

@@ -6,14 +6,15 @@ import { EditorState } from '@codemirror/state';
interface Props {
contentType: string;
useTemplating?: boolean;
defaultValue?: string | null;
onChange?: (value: string) => void;
}
export default function Editor({ contentType, defaultValue, onChange }: Props) {
export default function Editor({ contentType, useTemplating, defaultValue, onChange }: Props) {
const ref = useRef<HTMLDivElement>(null);
const extensions = useMemo(() => {
const ext = syntaxExtension(contentType);
const ext = syntaxExtension({ contentType, useTemplating });
return [
...baseExtensions,
...(ext ? [ext] : []),
@@ -28,13 +29,18 @@ export default function Editor({ contentType, defaultValue, onChange }: Props) {
useEffect(() => {
if (ref.current === null) return;
const view = new EditorView({
state: EditorState.create({
doc: defaultValue ?? '',
extensions: extensions,
}),
parent: ref.current,
});
let view: EditorView;
try {
view = new EditorView({
state: EditorState.create({
doc: defaultValue ?? '',
extensions: extensions,
}),
parent: ref.current,
});
} catch (e) {
console.log(e);
}
return () => view?.destroy();
}, [ref.current]);

View File

@@ -1,13 +1,18 @@
import { parser as twigParser } from './twig/twig';
import {
bracketMatching,
defaultHighlightStyle,
foldGutter,
foldInside,
foldKeymap,
foldNodeProp,
HighlightStyle,
indentNodeProp,
indentOnInput,
LanguageSupport,
LRLanguage,
syntaxHighlighting,
} from '@codemirror/language';
import { lintKeymap } from '@codemirror/lint';
import {
crosshairCursor,
drawSelection,
@@ -19,6 +24,12 @@ import {
lineNumbers,
rectangularSelection,
} from '@codemirror/view';
import { html } from '@codemirror/lang-html';
import { parseMixed } from '@lezer/common';
import { EditorState } from '@codemirror/state';
import { json } from '@codemirror/lang-json';
import { javascript } from '@codemirror/lang-javascript';
import { tags as t } from '@lezer/highlight';
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search';
import {
@@ -27,35 +38,86 @@ import {
closeBracketsKeymap,
completionKeymap,
} from '@codemirror/autocomplete';
import { lintKeymap } from '@codemirror/lint';
import { EditorState } from '@codemirror/state';
import { json } from '@codemirror/lang-json';
import { javascript } from '@codemirror/lang-javascript';
import { html } from '@codemirror/lang-html';
import { tags } from '@lezer/highlight';
import { placeholders } from './widgets';
export const myHighlightStyle = HighlightStyle.define([
{
tag: [tags.documentMeta, tags.blockComment, tags.lineComment, tags.docComment, tags.comment],
tag: [t.documentMeta, t.blockComment, t.lineComment, t.docComment, t.comment],
color: '#757b93',
},
{ tag: tags.name, color: '#4699de' },
{ tag: tags.variableName, color: '#31c434' },
{ tag: tags.bool, color: '#e864f6' },
{ tag: tags.attributeName, color: '#8f68ff' },
{ tag: tags.attributeValue, color: '#ff964b' },
{ tag: [tags.keyword, tags.string], color: '#e8b045' },
{ tag: tags.comment, color: '#cec4cc', fontStyle: 'italic' },
{ tag: [t.name], color: '#4699de' },
{ tag: [t.variableName], color: '#31c434' },
{ tag: [t.bool], color: '#e864f6' },
{ tag: [t.attributeName], color: '#8f68ff' },
{ tag: [t.attributeValue], color: '#ff964b' },
{ tag: [t.string], color: '#e8b045' },
{ tag: [t.keyword, t.meta], color: '#45e8a4' },
{ tag: [t.comment], color: '#cec4cc', fontStyle: 'italic' },
]);
const syntaxExtensions: Record<string, LanguageSupport> = {
'application/json': json(),
'application/javascript': javascript(),
'text/html': html(),
// export const defaultHighlightStyle = HighlightStyle.define([
// { tag: t.meta, color: '#404740' },
// { tag: t.link, textDecoration: 'underline' },
// { tag: t.heading, textDecoration: 'underline', fontWeight: 'bold' },
// { tag: t.emphasis, fontStyle: 'italic' },
// { tag: t.strong, fontWeight: 'bold' },
// { tag: t.strikethrough, textDecoration: 'line-through' },
// { tag: t.keyword, color: '#708' },
// { tag: [t.atom, t.bool, t.url, t.contentSeparator, t.labelName], color: '#219' },
// { tag: [t.literal, t.inserted], color: '#164' },
// { tag: [t.string, t.deleted], color: '#a11' },
// { tag: [t.regexp, t.escape, t.special(t.string)], color: '#e40' },
// { tag: t.definition(t.variableName), color: '#00f' },
// { tag: t.local(t.variableName), color: '#30a' },
// { tag: [t.typeName, t.namespace], color: '#085' },
// { tag: t.className, color: '#167' },
// { tag: [t.special(t.variableName), t.macroName], color: '#256' },
// { tag: t.definition(t.propertyName), color: '#00c' },
// { tag: t.comment, color: '#940' },
// { tag: t.invalid, color: '#f00' },
// ]);
const syntaxExtensions: Record<string, { base: LanguageSupport; ext: any[] }> = {
'application/json': { base: json(), ext: [] },
'application/javascript': { base: javascript(), ext: [] },
'text/html': { base: html(), ext: [] },
};
export function syntaxExtension(contentType: string): LanguageSupport | undefined {
return syntaxExtensions[contentType];
export function syntaxExtension({
contentType,
useTemplating,
}: {
contentType: string;
useTemplating?: boolean;
}) {
const { base, ext } = syntaxExtensions[contentType] ?? { base: json(), ext: [] };
if (!useTemplating) {
return [base];
}
const mixedTwigParser = twigParser.configure({
props: [
// Add basic folding/indent metadata
foldNodeProp.add({ Conditional: foldInside }),
indentNodeProp.add({
Conditional: (cx) => {
const closed = /^\s*\{% endif/.test(cx.textAfter);
return cx.lineIndent(cx.node.from) + (closed ? 0 : cx.unit);
},
}),
],
wrap: parseMixed((node) => {
return node.type.isTop
? {
parser: base.language.parser,
overlay: (node) => node.type.name == 'Text',
}
: null;
}),
});
const twigLanguage = LRLanguage.define({ parser: mixedTwigParser });
return [twigLanguage, placeholders, base.support, ...ext];
}
export const baseExtensions = [
@@ -78,7 +140,6 @@ export const baseExtensions = [
dropCursor(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
closeBrackets(),
autocompletion(),

View File

@@ -0,0 +1,7 @@
import { styleTags, tags as t } from '@lezer/highlight';
export const twigHighlight = styleTags({
'if endif': t.controlKeyword,
'{{ }} {% %}': t.meta,
DirectiveContent: t.variableName,
});

View File

@@ -0,0 +1,28 @@
// Very crude grammar for a subset of Twig templating syntax
@top Template { (directive | Text)* }
directive {
// Insert |
// Conditional { ConditionalOpen (directive | Text)* ConditionalClose }
Insert
}
@skip {space} {
Insert { "{{" DirectiveContent "}}" }
// ConditionalOpen { "{%" kw<"if"> DirectiveContent "%}" }
// ConditionalClose { "{%" kw<"endif"> "%}" }
}
kw<word> { @specialize[@name={word}]<Identifier, word> }
@tokens {
Identifier { @asciiLetter+ }
Text { ![{] Text? | "{" (@eof | ![%{] Text?) }
space { @whitespace+ }
DirectiveContent { ![%}] DirectiveContent? | $[%}] (@eof | ![}] DirectiveContent?) }
@precedence { space DirectiveContent }
"{{" "}}" // "{%" "%}"
}
@external propSource twigHighlight from "./twig-highlight.ts"

View File

@@ -0,0 +1,6 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
export const
Template = 1,
Insert = 2,
DirectiveContent = 4,
Text = 6

View File

@@ -0,0 +1,19 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import { LRParser } from '@lezer/lr';
import { twigHighlight } from './twig-highlight';
export const parser = LRParser.deserialize({
version: 14,
states: "zQVOPOOO_QQO'#C^OOOO'#Cc'#CcQVOPOOOdQQO,58xOOOO-E6a-E6aOOOO1G.d1G.d",
stateData: 'l~OYOS~ORPOUQO~OSSO~OTUO~OYS~',
goto: 'cWPPXPPPP]TQORQRORTR',
nodeNames: '⚠ Template Insert {{ DirectiveContent }} Text',
maxTerm: 10,
propSources: [twigHighlight],
skippedNodes: [0],
repeatNodeCount: 1,
tokenData:
",gRRmOX!|X^'u^p!|pq'uqu!|uv#pv#o!|#o#p)y#p#q!|#q#r+_#r#y!|#y#z'u#z$f!|$f$g'u$g#BY!|#BY#BZ'u#BZ$IS!|$IS$I_'u$I_$I|!|$I|$JO'u$JO$JT!|$JT$JU'u$JU$KV!|$KV$KW'u$KW&FU!|&FU&FV'u&FV;'S!|;'S;=`&f<%lO!|R#TXUPSQOu!|uv#pv#o!|#o#p$b#p#q!|#q#r#p#r;'S!|;'S;=`&f<%lO!|R#uXUPO#o!|#o#p$b#p#q!|#q#r&q#r;'S!|;'S;=`&f<%l~!|~O!|~~&aR$gZSQOu!|uv%Yv#o!|#o#p%o#p#q!|#q#r#p#r;'S!|;'S;=`&f<%l~!|~O!|~~&lQ%]UO#q%o#r;'S%o;'S;=`&Z<%l~%o~O%o~~&aQ%tVSQOu%ouv%Yv#q%o#q#r%Y#r;'S%o;'S;=`&Z<%lO%oQ&^P;=`<%l%oQ&fOSQR&iP;=`<%l!|P&qOUPP&vTUPO#o&q#o#p'V#p;'S&q;'S;=`'o<%lO&qP'YVOu&qv#o&q#p;'S&q;'S;=`'o<%l~&q~O&q~~&lP'rP;=`<%l&qR(OmUPYQSQOX!|X^'u^p!|pq'uqu!|uv#pv#o!|#o#p$b#p#q!|#q#r#p#r#y!|#y#z'u#z$f!|$f$g'u$g#BY!|#BY#BZ'u#BZ$IS!|$IS$I_'u$I_$I|!|$I|$JO'u$JO$JT!|$JT$JU'u$JU$KV!|$KV$KW'u$KW&FU!|&FU&FV'u&FV;'S!|;'S;=`&f<%lO!|R*OZSQOu!|uv%Yv#o!|#o#p*q#p#q!|#q#r#p#r;'S!|;'S;=`&f<%l~!|~O!|~~&lR*xVRPSQOu%ouv%Yv#q%o#q#r%Y#r;'S%o;'S;=`&Z<%lO%oR+dXUPO#o!|#o#p$b#p#q!|#q#r,P#r;'S!|;'S;=`&f<%l~!|~O!|~~&aR,WTTQUPO#o&q#o#p'V#p;'S&q;'S;=`'o<%lO&q",
tokenizers: [0, 1],
topRules: { Template: [0, 1] },
tokenPrec: 25,
});

View File

@@ -0,0 +1,84 @@
import {
Decoration,
DecorationSet,
EditorView,
MatchDecorator,
ViewPlugin,
ViewUpdate,
WidgetType,
} from '@codemirror/view';
class PlaceholderWidget extends WidgetType {
constructor(readonly name: string) {
super();
}
eq(other: PlaceholderWidget) {
return this.name == other.name;
}
toDOM() {
const elt = document.createElement('span');
elt.className = 'placeholder-widget';
elt.textContent = this.name;
return elt;
}
ignoreEvent() {
return false;
}
}
/**
* This is a custom MatchDecorator that will not decorate a match if the selection is inside it
*/
class BetterMatchDecorator extends MatchDecorator {
updateDeco(update: ViewUpdate, deco: DecorationSet): DecorationSet {
if (!update.startState.selection.eq(update.state.selection)) {
return super.createDeco(update.view);
} else {
return super.updateDeco(update, deco);
}
}
}
const placeholderMatcher = new BetterMatchDecorator({
regexp: /\{\{\s*([^}\s]+)\s*}}/g,
decoration(match, view, matchStartPos) {
const matchEndPos = matchStartPos + match[0].length - 1;
// Don't decorate if the cursor is inside the match
for (const r of view.state.selection.ranges) {
if (r.from > matchStartPos && r.to <= matchEndPos) return null;
}
const groupMatch = match[1];
if (groupMatch == null) {
// Should never happen, but make TS happy
console.warn('Group match was empty', match);
return null;
}
return Decoration.replace({
inclusive: true,
widget: new PlaceholderWidget(groupMatch),
});
},
});
export const placeholders = ViewPlugin.fromClass(
class {
placeholders: DecorationSet;
constructor(view: EditorView) {
this.placeholders = placeholderMatcher.createDeco(view);
}
update(update: ViewUpdate) {
console.log('VIEW UPDATE', update);
this.placeholders = placeholderMatcher.updateDeco(update, this.placeholders);
}
},
{
decorations: (instance) => instance.placeholders,
provide: (plugin) =>
EditorView.atomicRanges.of((view) => {
return view.plugin(plugin)?.placeholders || Decoration.none;
}),
},
);