From 2183d9e9a2dd68c1429f2e4f3a3f8bc5b1ba1330 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 1 Nov 2022 15:04:30 -0700 Subject: [PATCH] chore: use codemirror for editor (#18482) --- package-lock.json | 62 + package.json | 2 + .../playwright-core/ThirdPartyNotices.txt | 29 +- .../playwright-test/ThirdPartyNotices.txt | 29 +- packages/trace-viewer/package.json | 1 + packages/trace-viewer/src/ui/actionList.css | 4 + packages/trace-viewer/src/ui/sourceTab.tsx | 1 - packages/trace-viewer/vite.config.ts | 6 +- packages/trace-viewer/vite.sw.config.ts | 49 + packages/web/src/components/source.css | 132 +- packages/web/src/components/source.spec.tsx | 8 +- packages/web/src/components/source.tsx | 77 +- .../web/src/third_party/highlightjs/README.md | 4 - .../highlightjs/highlightjs/LICENSE | 29 - .../highlightjs/highlightjs/core.js | 2562 ----------------- .../highlightjs/highlightjs/github-dark.css | 10 - .../highlightjs/highlightjs/github.css | 10 - .../highlightjs/highlightjs/index.js | 12 - .../highlightjs/languages/csharp.js | 398 --- .../highlightjs/highlightjs/languages/java.js | 284 -- .../highlightjs/languages/javascript.js | 733 ----- .../highlightjs/languages/python.js | 432 --- .../web/src/third_party/highlightjs/roll.sh | 33 - tests/library/inspector/pause.spec.ts | 2 +- tests/library/trace-viewer.spec.ts | 4 - tests/playwright-test/reporter-html.spec.ts | 2 +- utils/build/build.js | 15 + utils/generate_third_party_notice.js | 38 +- 28 files changed, 385 insertions(+), 4583 deletions(-) create mode 100644 packages/trace-viewer/vite.sw.config.ts delete mode 100644 packages/web/src/third_party/highlightjs/README.md delete mode 100644 packages/web/src/third_party/highlightjs/highlightjs/LICENSE delete mode 100644 packages/web/src/third_party/highlightjs/highlightjs/core.js delete mode 100644 packages/web/src/third_party/highlightjs/highlightjs/github-dark.css delete mode 100644 packages/web/src/third_party/highlightjs/highlightjs/github.css delete mode 100644 packages/web/src/third_party/highlightjs/highlightjs/index.js delete mode 100644 packages/web/src/third_party/highlightjs/highlightjs/languages/csharp.js delete mode 100644 packages/web/src/third_party/highlightjs/highlightjs/languages/java.js delete mode 100644 packages/web/src/third_party/highlightjs/highlightjs/languages/javascript.js delete mode 100644 packages/web/src/third_party/highlightjs/highlightjs/languages/python.js delete mode 100755 packages/web/src/third_party/highlightjs/roll.sh diff --git a/package-lock.json b/package-lock.json index b1d67b58f1..9f1b950122 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.17.9", "@babel/plugin-transform-typescript": "^7.16.8", "@babel/preset-react": "^7.16.7", + "@types/codemirror": "^5.60.5", "@types/formidable": "^2.0.4", "@types/node": "=14.18.24", "@types/react": "^18.0.12", @@ -34,6 +35,7 @@ "@zip.js/zip.js": "^2.4.2", "ansi-to-html": "^0.7.2", "chokidar": "^3.5.3", + "codemirror": "^5.65.9", "colors": "^1.4.0", "commonmark": "^0.30.0", "concurrently": "^6.2.1", @@ -1035,6 +1037,21 @@ "node": ">=6" } }, + "node_modules/@types/codemirror": { + "version": "5.60.5", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz", + "integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==", + "dev": true, + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, "node_modules/@types/formidable": { "version": "2.0.4", "dev": true, @@ -1090,6 +1107,15 @@ "dev": true, "license": "MIT" }, + "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/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -1939,6 +1965,12 @@ "mimic-response": "^1.0.0" } }, + "node_modules/codemirror": { + "version": "5.65.9", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.9.tgz", + "integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw==", + "dev": true + }, "node_modules/color-convert": { "version": "1.9.3", "license": "MIT", @@ -6729,6 +6761,21 @@ "defer-to-connect": "^1.0.1" } }, + "@types/codemirror": { + "version": "5.60.5", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz", + "integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==", + "dev": true, + "requires": { + "@types/tern": "*" + } + }, + "@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, "@types/formidable": { "version": "2.0.4", "dev": true, @@ -6775,6 +6822,15 @@ "version": "0.16.1", "dev": true }, + "@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/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -7322,6 +7378,12 @@ "mimic-response": "^1.0.0" } }, + "codemirror": { + "version": "5.65.9", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.9.tgz", + "integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw==", + "dev": true + }, "color-convert": { "version": "1.9.3", "requires": { diff --git a/package.json b/package.json index 2b7f519a7c..5716007958 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.17.9", "@babel/plugin-transform-typescript": "^7.16.8", "@babel/preset-react": "^7.16.7", + "@types/codemirror": "^5.60.5", "@types/formidable": "^2.0.4", "@types/node": "=14.18.24", "@types/react": "^18.0.12", @@ -70,6 +71,7 @@ "@zip.js/zip.js": "^2.4.2", "ansi-to-html": "^0.7.2", "chokidar": "^3.5.3", + "codemirror": "^5.65.9", "colors": "^1.4.0", "commonmark": "^0.30.0", "concurrently": "^6.2.1", diff --git a/packages/playwright-core/ThirdPartyNotices.txt b/packages/playwright-core/ThirdPartyNotices.txt index 88ddec2d84..76cf98ba07 100644 --- a/packages/playwright-core/ThirdPartyNotices.txt +++ b/packages/playwright-core/ThirdPartyNotices.txt @@ -10,6 +10,7 @@ This project incorporates components from the projects listed below. The origina - balanced-match@1.0.2 (https://github.com/juliangruber/balanced-match) - brace-expansion@1.1.11 (https://github.com/juliangruber/brace-expansion) - buffer-crc32@0.2.13 (https://github.com/brianloveswords/buffer-crc32) +- codemirror@5.65.9 (https://github.com/codemirror/CodeMirror) - colors@1.4.0 (https://github.com/Marak/colors.js) - commander@8.3.0 (https://github.com/tj/commander.js) - concat-map@0.0.1 (https://github.com/substack/node-concat-map) @@ -328,6 +329,32 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEAL ========================================= END OF buffer-crc32@0.2.13 AND INFORMATION +%% codemirror@5.65.9 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (C) 2017 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF codemirror@5.65.9 AND INFORMATION + %% colors@1.4.0 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -1614,6 +1641,6 @@ END OF yazl@2.5.1 AND INFORMATION SUMMARY BEGIN HERE ========================================= -Total Packages: 45 +Total Packages: 46 ========================================= END OF SUMMARY \ No newline at end of file diff --git a/packages/playwright-test/ThirdPartyNotices.txt b/packages/playwright-test/ThirdPartyNotices.txt index 7f4514d451..f6b38f778b 100644 --- a/packages/playwright-test/ThirdPartyNotices.txt +++ b/packages/playwright-test/ThirdPartyNotices.txt @@ -84,6 +84,7 @@ This project incorporates components from the projects listed below. The origina - caniuse-lite@1.0.30001346 (https://github.com/browserslist/caniuse-lite) - chalk@2.4.2 (https://github.com/chalk/chalk) - chalk@4.1.2 (https://github.com/chalk/chalk) +- codemirror@5.65.9 (https://github.com/codemirror/CodeMirror) - color-convert@1.9.3 (https://github.com/Qix-/color-convert) - color-convert@2.0.1 (https://github.com/Qix-/color-convert) - color-name@1.1.3 (https://github.com/dfcreative/color-name) @@ -2744,6 +2745,32 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ========================================= END OF chalk@4.1.2 AND INFORMATION +%% codemirror@5.65.9 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (C) 2017 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF codemirror@5.65.9 AND INFORMATION + %% color-convert@1.9.3 NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (c) 2011-2016 Heather Arthur @@ -4029,6 +4056,6 @@ END OF to-regex-range@5.0.1 AND INFORMATION SUMMARY BEGIN HERE ========================================= -Total Packages: 138 +Total Packages: 139 ========================================= END OF SUMMARY \ No newline at end of file diff --git a/packages/trace-viewer/package.json b/packages/trace-viewer/package.json index eae4f1835c..069d430495 100644 --- a/packages/trace-viewer/package.json +++ b/packages/trace-viewer/package.json @@ -5,6 +5,7 @@ "scripts": { "dev": "vite", "build": "vite build && tsc", + "build-sw": "vite --config vite.sw.config.ts build && tsc", "preview": "vite preview" } } diff --git a/packages/trace-viewer/src/ui/actionList.css b/packages/trace-viewer/src/ui/actionList.css index 5bd31b7f86..3e776f1639 100644 --- a/packages/trace-viewer/src/ui/actionList.css +++ b/packages/trace-viewer/src/ui/actionList.css @@ -54,6 +54,10 @@ outline: 1px solid var(--vscode-focusBorder); } +.action-list-content:focus .action-entry.selected * { + color: var(--vscode-list-activeSelectionForeground); +} + .action-title { flex: auto; display: block; diff --git a/packages/trace-viewer/src/ui/sourceTab.tsx b/packages/trace-viewer/src/ui/sourceTab.tsx index c124f5ead2..0ff06ac4fa 100644 --- a/packages/trace-viewer/src/ui/sourceTab.tsx +++ b/packages/trace-viewer/src/ui/sourceTab.tsx @@ -18,7 +18,6 @@ import type { StackFrame } from '@protocol/channels'; import type { ActionTraceEvent } from '@trace/trace'; import { Source as SourceView } from '@web/components/source'; import { SplitView } from '@web/components/splitView'; -import '@web/third_party/highlightjs/highlightjs/github.css'; import * as React from 'react'; import { useAsyncMemo } from './helpers'; import './sourceTab.css'; diff --git a/packages/trace-viewer/vite.config.ts b/packages/trace-viewer/vite.config.ts index 5f82a436ac..0807316fc5 100644 --- a/packages/trace-viewer/vite.config.ts +++ b/packages/trace-viewer/vite.config.ts @@ -37,12 +37,8 @@ export default defineConfig({ outDir: path.resolve(__dirname, '../playwright-core/lib/webpack/traceViewer'), emptyOutDir: true, rollupOptions: { - input: { - main: path.resolve(__dirname, 'index.html'), - sw: path.resolve(__dirname, 'src/sw.ts'), - }, output: { - entryFileNames: info => info.name === 'sw' ? '[name].bundle.js' : '[name].[hash].js', + entryFileNames: () => '[name].[hash].js', assetFileNames: () => '[name].[hash][extname]', manualChunks: undefined, }, diff --git a/packages/trace-viewer/vite.sw.config.ts b/packages/trace-viewer/vite.sw.config.ts new file mode 100644 index 0000000000..8b23d6aac8 --- /dev/null +++ b/packages/trace-viewer/vite.sw.config.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { bundle } from './bundle'; +import * as path from 'path'; + +// https://vitejs.dev/config/ +export default defineConfig({ + base: '', + plugins: [ + react(), + bundle() + ], + resolve: { + alias: { + '@isomorphic': path.resolve(__dirname, '../playwright-core/src/server/isomorphic'), + '@protocol': path.resolve(__dirname, '../protocol/src'), + '@web': path.resolve(__dirname, '../web/src'), + }, + }, + build: { + outDir: path.resolve(__dirname, '../playwright-core/lib/webpack/traceViewer'), + rollupOptions: { + input: { + sw: path.resolve(__dirname, 'src/sw.ts'), + }, + output: { + entryFileNames: info => '[name].bundle.js', + assetFileNames: () => '[name].[hash][extname]', + manualChunks: undefined, + }, + }, + } +}); diff --git a/packages/web/src/components/source.css b/packages/web/src/components/source.css index 50de1f51e4..10f83268e2 100644 --- a/packages/web/src/components/source.css +++ b/packages/web/src/components/source.css @@ -15,8 +15,6 @@ */ @import '../third_party/vscode/colors.css'; -@import '../third_party/highlightjs/highlightjs/github.css'; -@import '../third_party/highlightjs/highlightjs/github-dark.css' (prefers-color-scheme: dark); .source { display: flex; @@ -32,21 +30,6 @@ color: var(--vscode-editor-foreground); } -.source-line { - display: flex; - flex: none; -} - -.source-line-number { - color: #555; - padding: 0 8px; - width: 40px; - margin-right: 3px; - text-align: right; - user-select: none; - flex: none; -} - .source-line-running { background-color: #b3dbff7f; z-index: 2; @@ -63,3 +46,118 @@ outline: 1px solid #ff5656; z-index: 2; } + +.cm-wrapper, .cm-wrapper > div { + width: 100%; + height: 100%; +} + +.CodeMirror span.cm-meta { + color: var(--vscode-editor-foreground); +} + +.CodeMirror span.cm-number { + color: var(--vscode-debugTokenExpression-number); +} + +.CodeMirror span.cm-keyword { + color: var(--vscode-debugTokenExpression-name); +} + +.CodeMirror span.cm-operator { + color: var(--vscode-editor-foreground); +} + +.CodeMirror span.cm-string { + color: var(--vscode-debugTokenExpression-string); +} + +.CodeMirror span.cm-string-2 { + color: var(--vscode-debugTokenExpression-string); +} + +.CodeMirror span.cm-error { + color: var(--vscode-errorForeground); +} + +.CodeMirror span.cm-def, .CodeMirror span.cm-tag { + color: #0070c1; +} + +.CodeMirror span.cm-comment, .CodeMirror span.cm-link { + color: #008000; +} + +.CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom { + color: #0070c1; +} + +.CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute { + color: #001080; +} + +.CodeMirror span.cm-variable-3, +.CodeMirror span.cm-type { + color: #267f99; +} + +@media(prefers-color-scheme: dark) { + + .CodeMirror span.cm-def, .CodeMirror span.cm-tag { + color: var(--vscode-debugView-valueChangedHighlight); + } + + .CodeMirror span.cm-comment, .CodeMirror span.cm-link { + color: #6a9955; + } + + .CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom { + color: #4fc1ff; + } + + .CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute { + color: #9cdcfe; + } + + .CodeMirror span.cm-variable-3, + .CodeMirror span.cm-type { + color: #4ec9b0; + } + +} + +.CodeMirror span.cm-bracket { + color: var(--vscode-editorBracketHighlight-foreground3); +} + +.CodeMirror-cursor { + border-left: 1px solid #bebebe; +} + +.CodeMirror div.CodeMirror-selected { + background: var(--vscode-terminal-inactiveSelectionBackground); +} + +.CodeMirror .CodeMirror-gutters { + background: var(--vscode-editor-background); + border-right: 1px solid var(--vscode-editorGroup-border); + color: var(--vscode-editorLineNumber-foreground); +} + +.CodeMirror .CodeMirror-matchingbracket { + background-color: var(--vscode-editorBracketPairGuide-background1); + color: var(--vscode-editorBracketHighlight-foreground1) !important; +} + + /* .CodeMirror-line, + .CodeMirror-line-like { + color: var(--vscode-editor-foreground) !important; + } */ + +.CodeMirror { + font-family: var(--vscode-editor-font-family) !important; + color: var(--vscode-editor-foreground) !important; + background-color: var(--vscode-editor-background) !important; + font-weight: var(--vscode-editor-font-weight) !important; + font-size: var(--vscode-editor-font-size) !important; +} diff --git a/packages/web/src/components/source.spec.tsx b/packages/web/src/components/source.spec.tsx index ee2f8b2f18..d363bfec3d 100644 --- a/packages/web/src/components/source.spec.tsx +++ b/packages/web/src/components/source.spec.tsx @@ -71,22 +71,22 @@ class Program test('highlight JavaScript', async ({ mount }) => { const component = await mount(); - await expect(component.locator('text="async"').first()).toHaveClass('hljs-keyword'); + await expect(component.locator('text="async"').first()).toHaveClass('cm-keyword'); }); test('highlight Python', async ({ mount }) => { const component = await mount(); - await expect(component.locator('text="async"').first()).toHaveClass('hljs-keyword'); + await expect(component.locator('text="async"').first()).toHaveClass('cm-keyword'); }); test('highlight Java', async ({ mount }) => { const component = await mount(); - await expect(component.locator('text="public"').first()).toHaveClass('hljs-keyword'); + await expect(component.locator('text="public"').first()).toHaveClass('cm-keyword'); }); test('highlight C#', async ({ mount }) => { const component = await mount(); - await expect(component.locator('text="public"').first()).toHaveClass('hljs-keyword'); + await expect(component.locator('text="public"').first()).toHaveClass('cm-keyword'); }); test('highlight lines', async ({ mount }) => { diff --git a/packages/web/src/components/source.tsx b/packages/web/src/components/source.tsx index 2099b0e432..1c4801fb69 100644 --- a/packages/web/src/components/source.tsx +++ b/packages/web/src/components/source.tsx @@ -16,7 +16,11 @@ import './source.css'; import * as React from 'react'; -import highlightjs from '../third_party/highlightjs/highlightjs'; +import CodeMirror from 'codemirror'; +import 'codemirror/mode/javascript/javascript'; +import 'codemirror/mode/python/python'; +import 'codemirror/mode/clike/clike'; +import 'codemirror/lib/codemirror.css'; export type SourceHighlight = { line: number; @@ -37,32 +41,51 @@ export const Source: React.FC = ({ highlight = [], revealLine }) => { - const lines = React.useMemo(() => { - const result = []; - let continuation: any; - for (const line of text.split('\n')) { - const highlighted = highlightjs.highlight(language, line, true, continuation); - continuation = highlighted.top; - result.push(highlighted.value); - } - return result; - }, [text, language]); + const codemirrorElement = React.createRef(); + const [codemirror, setCodemirror] = React.useState(); - const revealedLineRef = React.createRef(); - React.useLayoutEffect(() => { - if (typeof revealLine === 'number' && revealedLineRef.current) - revealedLineRef.current.scrollIntoView({ block: 'center', inline: 'nearest' }); - }, [revealedLineRef, revealLine]); + React.useEffect(() => { + let mode; + if (language === 'javascript') + mode = 'javascript'; + if (language === 'python') + mode = 'python'; + if (language === 'java') + mode = 'text/x-java'; + if (language === 'csharp') + mode = 'text/x-csharp'; - return
{ - lines.map((markup, index) => { - const lineNumber = index + 1; - const lineHighlight = highlight.find(h => h.line === lineNumber); - const lineClass = lineHighlight ? `source-line source-line-${lineHighlight.type}` : 'source-line'; - return
-
{lineNumber}
-
-
; - }) - }
; + if (codemirror && codemirror.getOption('mode') === mode) + return; + + if (!codemirrorElement.current) + return; + if (codemirror) + codemirror.getWrapperElement().remove(); + + const cm = CodeMirror(codemirrorElement.current, { + value: '', + mode, + readOnly: true, + lineNumbers: true, + }); + setCodemirror(cm); + updateEditor(cm, text, highlight, revealLine); + }, [codemirror, codemirrorElement, text, language, highlight, revealLine]); + + if (codemirror) + updateEditor(codemirror, text, highlight, revealLine); + + return
; }; + +function updateEditor(cm: CodeMirror.Editor, text: string, highlight: SourceHighlight[], revealLine: number | undefined) { + if (cm.getValue() !== text) + cm.setValue(text); + for (let i = 0; i < cm.lineCount(); ++i) + cm.removeLineClass(i, 'wrap'); + for (const h of highlight) + cm.addLineClass(h.line - 1, 'wrap', `source-line-${h.type}`); + if (revealLine) + cm.scrollIntoView({ line: revealLine - 1, ch: 0 }, 50); +} diff --git a/packages/web/src/third_party/highlightjs/README.md b/packages/web/src/third_party/highlightjs/README.md deleted file mode 100644 index 5f79f06cb2..0000000000 --- a/packages/web/src/third_party/highlightjs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Highlight.js - -This is a small build of [highlight.js](https://github.com/highlightjs/highlight.js). -Pick a [stable release](https://github.com/highlightjs/highlight.js/releases) revision and use `roll.sh` to update. diff --git a/packages/web/src/third_party/highlightjs/highlightjs/LICENSE b/packages/web/src/third_party/highlightjs/highlightjs/LICENSE deleted file mode 100644 index 2250cc7eca..0000000000 --- a/packages/web/src/third_party/highlightjs/highlightjs/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2006, Ivan Sagalaev. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/web/src/third_party/highlightjs/highlightjs/core.js b/packages/web/src/third_party/highlightjs/highlightjs/core.js deleted file mode 100644 index 8b59499704..0000000000 --- a/packages/web/src/third_party/highlightjs/highlightjs/core.js +++ /dev/null @@ -1,2562 +0,0 @@ -var deepFreezeEs6 = {exports: {}}; - -function deepFreeze(obj) { - if (obj instanceof Map) { - obj.clear = obj.delete = obj.set = function () { - throw new Error('map is read-only'); - }; - } else if (obj instanceof Set) { - obj.add = obj.clear = obj.delete = function () { - throw new Error('set is read-only'); - }; - } - - // Freeze self - Object.freeze(obj); - - Object.getOwnPropertyNames(obj).forEach(function (name) { - var prop = obj[name]; - - // Freeze prop if it is an object - if (typeof prop == 'object' && !Object.isFrozen(prop)) { - deepFreeze(prop); - } - }); - - return obj; -} - -deepFreezeEs6.exports = deepFreeze; -deepFreezeEs6.exports.default = deepFreeze; - -/** @typedef {import('highlight.js').CallbackResponse} CallbackResponse */ -/** @typedef {import('highlight.js').CompiledMode} CompiledMode */ -/** @implements CallbackResponse */ - -class Response { - /** - * @param {CompiledMode} mode - */ - constructor(mode) { - // eslint-disable-next-line no-undefined - if (mode.data === undefined) mode.data = {}; - - this.data = mode.data; - this.isMatchIgnored = false; - } - - ignoreMatch() { - this.isMatchIgnored = true; - } -} - -/** - * @param {string} value - * @returns {string} - */ -function escapeHTML(value) { - return value - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} - -/** - * performs a shallow merge of multiple objects into one - * - * @template T - * @param {T} original - * @param {Record[]} objects - * @returns {T} a single new object - */ -function inherit$1(original, ...objects) { - /** @type Record */ - const result = Object.create(null); - - for (const key in original) { - result[key] = original[key]; - } - objects.forEach(function(obj) { - for (const key in obj) { - result[key] = obj[key]; - } - }); - return /** @type {T} */ (result); -} - -/** - * @typedef {object} Renderer - * @property {(text: string) => void} addText - * @property {(node: Node) => void} openNode - * @property {(node: Node) => void} closeNode - * @property {() => string} value - */ - -/** @typedef {{scope?: string, language?: string, sublanguage?: boolean}} Node */ -/** @typedef {{walk: (r: Renderer) => void}} Tree */ -/** */ - -const SPAN_CLOSE = ''; - -/** - * Determines if a node needs to be wrapped in - * - * @param {Node} node */ -const emitsWrappingTags = (node) => { - // rarely we can have a sublanguage where language is undefined - // TODO: track down why - return !!node.scope || (node.sublanguage && node.language); -}; - -/** - * - * @param {string} name - * @param {{prefix:string}} options - */ -const scopeToCSSClass = (name, { prefix }) => { - if (name.includes(".")) { - const pieces = name.split("."); - return [ - `${prefix}${pieces.shift()}`, - ...(pieces.map((x, i) => `${x}${"_".repeat(i + 1)}`)) - ].join(" "); - } - return `${prefix}${name}`; -}; - -/** @type {Renderer} */ -class HTMLRenderer { - /** - * Creates a new HTMLRenderer - * - * @param {Tree} parseTree - the parse tree (must support `walk` API) - * @param {{classPrefix: string}} options - */ - constructor(parseTree, options) { - this.buffer = ""; - this.classPrefix = options.classPrefix; - parseTree.walk(this); - } - - /** - * Adds texts to the output stream - * - * @param {string} text */ - addText(text) { - this.buffer += escapeHTML(text); - } - - /** - * Adds a node open to the output stream (if needed) - * - * @param {Node} node */ - openNode(node) { - if (!emitsWrappingTags(node)) return; - - let className = ""; - if (node.sublanguage) { - className = `language-${node.language}`; - } else { - className = scopeToCSSClass(node.scope, { prefix: this.classPrefix }); - } - this.span(className); - } - - /** - * Adds a node close to the output stream (if needed) - * - * @param {Node} node */ - closeNode(node) { - if (!emitsWrappingTags(node)) return; - - this.buffer += SPAN_CLOSE; - } - - /** - * returns the accumulated buffer - */ - value() { - return this.buffer; - } - - // helpers - - /** - * Builds a span element - * - * @param {string} className */ - span(className) { - this.buffer += ``; - } -} - -/** @typedef {{scope?: string, language?: string, sublanguage?: boolean, children: Node[]} | string} Node */ -/** @typedef {{scope?: string, language?: string, sublanguage?: boolean, children: Node[]} } DataNode */ -/** @typedef {import('highlight.js').Emitter} Emitter */ -/** */ - -/** @returns {DataNode} */ -const newNode = (opts = {}) => { - /** @type DataNode */ - const result = { children: [] }; - Object.assign(result, opts); - return result; -}; - -class TokenTree { - constructor() { - /** @type DataNode */ - this.rootNode = newNode(); - this.stack = [this.rootNode]; - } - - get top() { - return this.stack[this.stack.length - 1]; - } - - get root() { return this.rootNode; } - - /** @param {Node} node */ - add(node) { - this.top.children.push(node); - } - - /** @param {string} scope */ - openNode(scope) { - /** @type Node */ - const node = newNode({ scope }); - this.add(node); - this.stack.push(node); - } - - closeNode() { - if (this.stack.length > 1) { - return this.stack.pop(); - } - // eslint-disable-next-line no-undefined - return undefined; - } - - closeAllNodes() { - while (this.closeNode()); - } - - toJSON() { - return JSON.stringify(this.rootNode, null, 4); - } - - /** - * @typedef { import("./html_renderer").Renderer } Renderer - * @param {Renderer} builder - */ - walk(builder) { - // this does not - return this.constructor._walk(builder, this.rootNode); - // this works - // return TokenTree._walk(builder, this.rootNode); - } - - /** - * @param {Renderer} builder - * @param {Node} node - */ - static _walk(builder, node) { - if (typeof node === "string") { - builder.addText(node); - } else if (node.children) { - builder.openNode(node); - node.children.forEach((child) => this._walk(builder, child)); - builder.closeNode(node); - } - return builder; - } - - /** - * @param {Node} node - */ - static _collapse(node) { - if (typeof node === "string") return; - if (!node.children) return; - - if (node.children.every(el => typeof el === "string")) { - // node.text = node.children.join(""); - // delete node.children; - node.children = [node.children.join("")]; - } else { - node.children.forEach((child) => { - TokenTree._collapse(child); - }); - } - } -} - -/** - Currently this is all private API, but this is the minimal API necessary - that an Emitter must implement to fully support the parser. - - Minimal interface: - - - addKeyword(text, scope) - - addText(text) - - addSublanguage(emitter, subLanguageName) - - finalize() - - openNode(scope) - - closeNode() - - closeAllNodes() - - toHTML() - -*/ - -/** - * @implements {Emitter} - */ -class TokenTreeEmitter extends TokenTree { - /** - * @param {*} options - */ - constructor(options) { - super(); - this.options = options; - } - - /** - * @param {string} text - * @param {string} scope - */ - addKeyword(text, scope) { - if (text === "") { return; } - - this.openNode(scope); - this.addText(text); - this.closeNode(); - } - - /** - * @param {string} text - */ - addText(text) { - if (text === "") { return; } - - this.add(text); - } - - /** - * @param {Emitter & {root: DataNode}} emitter - * @param {string} name - */ - addSublanguage(emitter, name) { - /** @type DataNode */ - const node = emitter.root; - node.sublanguage = true; - node.language = name; - this.add(node); - } - - toHTML() { - const renderer = new HTMLRenderer(this, this.options); - return renderer.value(); - } - - finalize() { - return true; - } -} - -/** - * @param {string} value - * @returns {RegExp} - * */ - -/** - * @param {RegExp | string } re - * @returns {string} - */ -function source(re) { - if (!re) return null; - if (typeof re === "string") return re; - - return re.source; -} - -/** - * @param {RegExp | string } re - * @returns {string} - */ -function lookahead(re) { - return concat('(?=', re, ')'); -} - -/** - * @param {RegExp | string } re - * @returns {string} - */ -function anyNumberOfTimes(re) { - return concat('(?:', re, ')*'); -} - -/** - * @param {RegExp | string } re - * @returns {string} - */ -function optional(re) { - return concat('(?:', re, ')?'); -} - -/** - * @param {...(RegExp | string) } args - * @returns {string} - */ -function concat(...args) { - const joined = args.map((x) => source(x)).join(""); - return joined; -} - -/** - * @param { Array } args - * @returns {object} - */ -function stripOptionsFromArgs(args) { - const opts = args[args.length - 1]; - - if (typeof opts === 'object' && opts.constructor === Object) { - args.splice(args.length - 1, 1); - return opts; - } else { - return {}; - } -} - -/** @typedef { {capture?: boolean} } RegexEitherOptions */ - -/** - * Any of the passed expresssions may match - * - * Creates a huge this | this | that | that match - * @param {(RegExp | string)[] | [...(RegExp | string)[], RegexEitherOptions]} args - * @returns {string} - */ -function either(...args) { - /** @type { object & {capture?: boolean} } */ - const opts = stripOptionsFromArgs(args); - const joined = '(' - + (opts.capture ? "" : "?:") - + args.map((x) => source(x)).join("|") + ")"; - return joined; -} - -/** - * @param {RegExp | string} re - * @returns {number} - */ -function countMatchGroups(re) { - return (new RegExp(re.toString() + '|')).exec('').length - 1; -} - -/** - * Does lexeme start with a regular expression match at the beginning - * @param {RegExp} re - * @param {string} lexeme - */ -function startsWith(re, lexeme) { - const match = re && re.exec(lexeme); - return match && match.index === 0; -} - -// BACKREF_RE matches an open parenthesis or backreference. To avoid -// an incorrect parse, it additionally matches the following: -// - [...] elements, where the meaning of parentheses and escapes change -// - other escape sequences, so we do not misparse escape sequences as -// interesting elements -// - non-matching or lookahead parentheses, which do not capture. These -// follow the '(' with a '?'. -const BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./; - -// **INTERNAL** Not intended for outside usage -// join logically computes regexps.join(separator), but fixes the -// backreferences so they continue to match. -// it also places each individual regular expression into it's own -// match group, keeping track of the sequencing of those match groups -// is currently an exercise for the caller. :-) -/** - * @param {(string | RegExp)[]} regexps - * @param {{joinWith: string}} opts - * @returns {string} - */ -function _rewriteBackreferences(regexps, { joinWith }) { - let numCaptures = 0; - - return regexps.map((regex) => { - numCaptures += 1; - const offset = numCaptures; - let re = source(regex); - let out = ''; - - while (re.length > 0) { - const match = BACKREF_RE.exec(re); - if (!match) { - out += re; - break; - } - out += re.substring(0, match.index); - re = re.substring(match.index + match[0].length); - if (match[0][0] === '\\' && match[1]) { - // Adjust the backreference. - out += '\\' + String(Number(match[1]) + offset); - } else { - out += match[0]; - if (match[0] === '(') { - numCaptures++; - } - } - } - return out; - }).map(re => `(${re})`).join(joinWith); -} - -/** @typedef {import('highlight.js').Mode} Mode */ -/** @typedef {import('highlight.js').ModeCallback} ModeCallback */ - -// Common regexps -const MATCH_NOTHING_RE = /\b\B/; -const IDENT_RE = '[a-zA-Z]\\w*'; -const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; -const NUMBER_RE = '\\b\\d+(\\.\\d+)?'; -const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float -const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... -const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; - -/** -* @param { Partial & {binary?: string | RegExp} } opts -*/ -const SHEBANG = (opts = {}) => { - const beginShebang = /^#![ ]*\//; - if (opts.binary) { - opts.begin = concat( - beginShebang, - /.*\b/, - opts.binary, - /\b.*/); - } - return inherit$1({ - scope: 'meta', - begin: beginShebang, - end: /$/, - relevance: 0, - /** @type {ModeCallback} */ - "on:begin": (m, resp) => { - if (m.index !== 0) resp.ignoreMatch(); - } - }, opts); -}; - -// Common modes -const BACKSLASH_ESCAPE = { - begin: '\\\\[\\s\\S]', relevance: 0 -}; -const APOS_STRING_MODE = { - scope: 'string', - begin: '\'', - end: '\'', - illegal: '\\n', - contains: [BACKSLASH_ESCAPE] -}; -const QUOTE_STRING_MODE = { - scope: 'string', - begin: '"', - end: '"', - illegal: '\\n', - contains: [BACKSLASH_ESCAPE] -}; -const PHRASAL_WORDS_MODE = { - begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ -}; -/** - * Creates a comment mode - * - * @param {string | RegExp} begin - * @param {string | RegExp} end - * @param {Mode | {}} [modeOptions] - * @returns {Partial} - */ -const COMMENT = function(begin, end, modeOptions = {}) { - const mode = inherit$1( - { - scope: 'comment', - begin, - end, - contains: [] - }, - modeOptions - ); - mode.contains.push({ - scope: 'doctag', - // hack to avoid the space from being included. the space is necessary to - // match here to prevent the plain text rule below from gobbling up doctags - begin: '[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)', - end: /(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/, - excludeBegin: true, - relevance: 0 - }); - const ENGLISH_WORD = either( - // list of common 1 and 2 letter words in English - "I", - "a", - "is", - "so", - "us", - "to", - "at", - "if", - "in", - "it", - "on", - // note: this is not an exhaustive list of contractions, just popular ones - /[A-Za-z]+['](d|ve|re|ll|t|s|n)/, // contractions - can't we'd they're let's, etc - /[A-Za-z]+[-][a-z]+/, // `no-way`, etc. - /[A-Za-z][a-z]{2,}/ // allow capitalized words at beginning of sentences - ); - // looking like plain text, more likely to be a comment - mode.contains.push( - { - // TODO: how to include ", (, ) without breaking grammars that use these for - // comment delimiters? - // begin: /[ ]+([()"]?([A-Za-z'-]{3,}|is|a|I|so|us|[tT][oO]|at|if|in|it|on)[.]?[()":]?([.][ ]|[ ]|\))){3}/ - // --- - - // this tries to find sequences of 3 english words in a row (without any - // "programming" type syntax) this gives us a strong signal that we've - // TRULY found a comment - vs perhaps scanning with the wrong language. - // It's possible to find something that LOOKS like the start of the - // comment - but then if there is no readable text - good chance it is a - // false match and not a comment. - // - // for a visual example please see: - // https://github.com/highlightjs/highlight.js/issues/2827 - - begin: concat( - /[ ]+/, // necessary to prevent us gobbling up doctags like /* @author Bob Mcgill */ - '(', - ENGLISH_WORD, - /[.]?[:]?([.][ ]|[ ])/, - '){3}') // look for 3 words in a row - } - ); - return mode; -}; -const C_LINE_COMMENT_MODE = COMMENT('//', '$'); -const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/'); -const HASH_COMMENT_MODE = COMMENT('#', '$'); -const NUMBER_MODE = { - scope: 'number', - begin: NUMBER_RE, - relevance: 0 -}; -const C_NUMBER_MODE = { - scope: 'number', - begin: C_NUMBER_RE, - relevance: 0 -}; -const BINARY_NUMBER_MODE = { - scope: 'number', - begin: BINARY_NUMBER_RE, - relevance: 0 -}; -const REGEXP_MODE = { - // this outer rule makes sure we actually have a WHOLE regex and not simply - // an expression such as: - // - // 3 / something - // - // (which will then blow up when regex's `illegal` sees the newline) - begin: /(?=\/[^/\n]*\/)/, - contains: [{ - scope: 'regexp', - begin: /\//, - end: /\/[gimuy]*/, - illegal: /\n/, - contains: [ - BACKSLASH_ESCAPE, - { - begin: /\[/, - end: /\]/, - relevance: 0, - contains: [BACKSLASH_ESCAPE] - } - ] - }] -}; -const TITLE_MODE = { - scope: 'title', - begin: IDENT_RE, - relevance: 0 -}; -const UNDERSCORE_TITLE_MODE = { - scope: 'title', - begin: UNDERSCORE_IDENT_RE, - relevance: 0 -}; -const METHOD_GUARD = { - // excludes method names from keyword processing - begin: '\\.\\s*' + UNDERSCORE_IDENT_RE, - relevance: 0 -}; - -/** - * Adds end same as begin mechanics to a mode - * - * Your mode must include at least a single () match group as that first match - * group is what is used for comparison - * @param {Partial} mode - */ -const END_SAME_AS_BEGIN = function(mode) { - return Object.assign(mode, - { - /** @type {ModeCallback} */ - 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; }, - /** @type {ModeCallback} */ - 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); } - }); -}; - -var MODES = /*#__PURE__*/Object.freeze({ - __proto__: null, - MATCH_NOTHING_RE: MATCH_NOTHING_RE, - IDENT_RE: IDENT_RE, - UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE, - NUMBER_RE: NUMBER_RE, - C_NUMBER_RE: C_NUMBER_RE, - BINARY_NUMBER_RE: BINARY_NUMBER_RE, - RE_STARTERS_RE: RE_STARTERS_RE, - SHEBANG: SHEBANG, - BACKSLASH_ESCAPE: BACKSLASH_ESCAPE, - APOS_STRING_MODE: APOS_STRING_MODE, - QUOTE_STRING_MODE: QUOTE_STRING_MODE, - PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE, - COMMENT: COMMENT, - C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE, - C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE, - HASH_COMMENT_MODE: HASH_COMMENT_MODE, - NUMBER_MODE: NUMBER_MODE, - C_NUMBER_MODE: C_NUMBER_MODE, - BINARY_NUMBER_MODE: BINARY_NUMBER_MODE, - REGEXP_MODE: REGEXP_MODE, - TITLE_MODE: TITLE_MODE, - UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE, - METHOD_GUARD: METHOD_GUARD, - END_SAME_AS_BEGIN: END_SAME_AS_BEGIN -}); - -/** -@typedef {import('highlight.js').CallbackResponse} CallbackResponse -@typedef {import('highlight.js').CompilerExt} CompilerExt -*/ - -// Grammar extensions / plugins -// See: https://github.com/highlightjs/highlight.js/issues/2833 - -// Grammar extensions allow "syntactic sugar" to be added to the grammar modes -// without requiring any underlying changes to the compiler internals. - -// `compileMatch` being the perfect small example of now allowing a grammar -// author to write `match` when they desire to match a single expression rather -// than being forced to use `begin`. The extension then just moves `match` into -// `begin` when it runs. Ie, no features have been added, but we've just made -// the experience of writing (and reading grammars) a little bit nicer. - -// ------ - -// TODO: We need negative look-behind support to do this properly -/** - * Skip a match if it has a preceding dot - * - * This is used for `beginKeywords` to prevent matching expressions such as - * `bob.keyword.do()`. The mode compiler automatically wires this up as a - * special _internal_ 'on:begin' callback for modes with `beginKeywords` - * @param {RegExpMatchArray} match - * @param {CallbackResponse} response - */ -function skipIfHasPrecedingDot(match, response) { - const before = match.input[match.index - 1]; - if (before === ".") { - response.ignoreMatch(); - } -} - -/** - * - * @type {CompilerExt} - */ -function scopeClassName(mode, _parent) { - // eslint-disable-next-line no-undefined - if (mode.className !== undefined) { - mode.scope = mode.className; - delete mode.className; - } -} - -/** - * `beginKeywords` syntactic sugar - * @type {CompilerExt} - */ -function beginKeywords(mode, parent) { - if (!parent) return; - if (!mode.beginKeywords) return; - - // for languages with keywords that include non-word characters checking for - // a word boundary is not sufficient, so instead we check for a word boundary - // or whitespace - this does no harm in any case since our keyword engine - // doesn't allow spaces in keywords anyways and we still check for the boundary - // first - mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)'; - mode.__beforeBegin = skipIfHasPrecedingDot; - mode.keywords = mode.keywords || mode.beginKeywords; - delete mode.beginKeywords; - - // prevents double relevance, the keywords themselves provide - // relevance, the mode doesn't need to double it - // eslint-disable-next-line no-undefined - if (mode.relevance === undefined) mode.relevance = 0; -} - -/** - * Allow `illegal` to contain an array of illegal values - * @type {CompilerExt} - */ -function compileIllegal(mode, _parent) { - if (!Array.isArray(mode.illegal)) return; - - mode.illegal = either(...mode.illegal); -} - -/** - * `match` to match a single expression for readability - * @type {CompilerExt} - */ -function compileMatch(mode, _parent) { - if (!mode.match) return; - if (mode.begin || mode.end) throw new Error("begin & end are not supported with match"); - - mode.begin = mode.match; - delete mode.match; -} - -/** - * provides the default 1 relevance to all modes - * @type {CompilerExt} - */ -function compileRelevance(mode, _parent) { - // eslint-disable-next-line no-undefined - if (mode.relevance === undefined) mode.relevance = 1; -} - -// allow beforeMatch to act as a "qualifier" for the match -// the full match begin must be [beforeMatch][begin] -const beforeMatchExt = (mode, parent) => { - if (!mode.beforeMatch) return; - // starts conflicts with endsParent which we need to make sure the child - // rule is not matched multiple times - if (mode.starts) throw new Error("beforeMatch cannot be used with starts"); - - const originalMode = Object.assign({}, mode); - Object.keys(mode).forEach((key) => { delete mode[key]; }); - - mode.keywords = originalMode.keywords; - mode.begin = concat(originalMode.beforeMatch, lookahead(originalMode.begin)); - mode.starts = { - relevance: 0, - contains: [ - Object.assign(originalMode, { endsParent: true }) - ] - }; - mode.relevance = 0; - - delete originalMode.beforeMatch; -}; - -// keywords that should have no default relevance value -const COMMON_KEYWORDS = [ - 'of', - 'and', - 'for', - 'in', - 'not', - 'or', - 'if', - 'then', - 'parent', // common variable name - 'list', // common variable name - 'value' // common variable name -]; - -const DEFAULT_KEYWORD_SCOPE = "keyword"; - -/** - * Given raw keywords from a language definition, compile them. - * - * @param {string | Record | Array} rawKeywords - * @param {boolean} caseInsensitive - */ -function compileKeywords(rawKeywords, caseInsensitive, scopeName = DEFAULT_KEYWORD_SCOPE) { - /** @type KeywordDict */ - const compiledKeywords = Object.create(null); - - // input can be a string of keywords, an array of keywords, or a object with - // named keys representing scopeName (which can then point to a string or array) - if (typeof rawKeywords === 'string') { - compileList(scopeName, rawKeywords.split(" ")); - } else if (Array.isArray(rawKeywords)) { - compileList(scopeName, rawKeywords); - } else { - Object.keys(rawKeywords).forEach(function(scopeName) { - // collapse all our objects back into the parent object - Object.assign( - compiledKeywords, - compileKeywords(rawKeywords[scopeName], caseInsensitive, scopeName) - ); - }); - } - return compiledKeywords; - - // --- - - /** - * Compiles an individual list of keywords - * - * Ex: "for if when while|5" - * - * @param {string} scopeName - * @param {Array} keywordList - */ - function compileList(scopeName, keywordList) { - if (caseInsensitive) { - keywordList = keywordList.map(x => x.toLowerCase()); - } - keywordList.forEach(function(keyword) { - const pair = keyword.split('|'); - compiledKeywords[pair[0]] = [scopeName, scoreForKeyword(pair[0], pair[1])]; - }); - } -} - -/** - * Returns the proper score for a given keyword - * - * Also takes into account comment keywords, which will be scored 0 UNLESS - * another score has been manually assigned. - * @param {string} keyword - * @param {string} [providedScore] - */ -function scoreForKeyword(keyword, providedScore) { - // manual scores always win over common keywords - // so you can force a score of 1 if you really insist - if (providedScore) { - return Number(providedScore); - } - - return commonKeyword(keyword) ? 0 : 1; -} - -/** - * Determines if a given keyword is common or not - * - * @param {string} keyword */ -function commonKeyword(keyword) { - return COMMON_KEYWORDS.includes(keyword.toLowerCase()); -} - -/* - -For the reasoning behind this please see: -https://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419 - -*/ - -/** - * @type {Record} - */ -const seenDeprecations = {}; - -/** - * @param {string} message - */ -const error = (message) => { - console.error(message); -}; - -/** - * @param {string} message - * @param {any} args - */ -const warn = (message, ...args) => { - console.log(`WARN: ${message}`, ...args); -}; - -/** - * @param {string} version - * @param {string} message - */ -const deprecated = (version, message) => { - if (seenDeprecations[`${version}/${message}`]) return; - - console.log(`Deprecated as of ${version}. ${message}`); - seenDeprecations[`${version}/${message}`] = true; -}; - -/* eslint-disable no-throw-literal */ - -/** -@typedef {import('highlight.js').CompiledMode} CompiledMode -*/ - -const MultiClassError = new Error(); - -/** - * Renumbers labeled scope names to account for additional inner match - * groups that otherwise would break everything. - * - * Lets say we 3 match scopes: - * - * { 1 => ..., 2 => ..., 3 => ... } - * - * So what we need is a clean match like this: - * - * (a)(b)(c) => [ "a", "b", "c" ] - * - * But this falls apart with inner match groups: - * - * (a)(((b)))(c) => ["a", "b", "b", "b", "c" ] - * - * Our scopes are now "out of alignment" and we're repeating `b` 3 times. - * What needs to happen is the numbers are remapped: - * - * { 1 => ..., 2 => ..., 5 => ... } - * - * We also need to know that the ONLY groups that should be output - * are 1, 2, and 5. This function handles this behavior. - * - * @param {CompiledMode} mode - * @param {Array} regexes - * @param {{key: "beginScope"|"endScope"}} opts - */ -function remapScopeNames(mode, regexes, { key }) { - let offset = 0; - const scopeNames = mode[key]; - /** @type Record */ - const emit = {}; - /** @type Record */ - const positions = {}; - - for (let i = 1; i <= regexes.length; i++) { - positions[i + offset] = scopeNames[i]; - emit[i + offset] = true; - offset += countMatchGroups(regexes[i - 1]); - } - // we use _emit to keep track of which match groups are "top-level" to avoid double - // output from inside match groups - mode[key] = positions; - mode[key]._emit = emit; - mode[key]._multi = true; -} - -/** - * @param {CompiledMode} mode - */ -function beginMultiClass(mode) { - if (!Array.isArray(mode.begin)) return; - - if (mode.skip || mode.excludeBegin || mode.returnBegin) { - error("skip, excludeBegin, returnBegin not compatible with beginScope: {}"); - throw MultiClassError; - } - - if (typeof mode.beginScope !== "object" || mode.beginScope === null) { - error("beginScope must be object"); - throw MultiClassError; - } - - remapScopeNames(mode, mode.begin, { key: "beginScope" }); - mode.begin = _rewriteBackreferences(mode.begin, { joinWith: "" }); -} - -/** - * @param {CompiledMode} mode - */ -function endMultiClass(mode) { - if (!Array.isArray(mode.end)) return; - - if (mode.skip || mode.excludeEnd || mode.returnEnd) { - error("skip, excludeEnd, returnEnd not compatible with endScope: {}"); - throw MultiClassError; - } - - if (typeof mode.endScope !== "object" || mode.endScope === null) { - error("endScope must be object"); - throw MultiClassError; - } - - remapScopeNames(mode, mode.end, { key: "endScope" }); - mode.end = _rewriteBackreferences(mode.end, { joinWith: "" }); -} - -/** - * this exists only to allow `scope: {}` to be used beside `match:` - * Otherwise `beginScope` would necessary and that would look weird - - { - match: [ /def/, /\w+/ ] - scope: { 1: "keyword" , 2: "title" } - } - - * @param {CompiledMode} mode - */ -function scopeSugar(mode) { - if (mode.scope && typeof mode.scope === "object" && mode.scope !== null) { - mode.beginScope = mode.scope; - delete mode.scope; - } -} - -/** - * @param {CompiledMode} mode - */ -function MultiClass(mode) { - scopeSugar(mode); - - if (typeof mode.beginScope === "string") { - mode.beginScope = { _wrap: mode.beginScope }; - } - if (typeof mode.endScope === "string") { - mode.endScope = { _wrap: mode.endScope }; - } - - beginMultiClass(mode); - endMultiClass(mode); -} - -/** -@typedef {import('highlight.js').Mode} Mode -@typedef {import('highlight.js').CompiledMode} CompiledMode -@typedef {import('highlight.js').Language} Language -@typedef {import('highlight.js').HLJSPlugin} HLJSPlugin -@typedef {import('highlight.js').CompiledLanguage} CompiledLanguage -*/ - -// compilation - -/** - * Compiles a language definition result - * - * Given the raw result of a language definition (Language), compiles this so - * that it is ready for highlighting code. - * @param {Language} language - * @returns {CompiledLanguage} - */ -function compileLanguage(language) { - /** - * Builds a regex with the case sensitivity of the current language - * - * @param {RegExp | string} value - * @param {boolean} [global] - */ - function langRe(value, global) { - return new RegExp( - source(value), - 'm' - + (language.case_insensitive ? 'i' : '') - + (language.unicodeRegex ? 'u' : '') - + (global ? 'g' : '') - ); - } - - /** - Stores multiple regular expressions and allows you to quickly search for - them all in a string simultaneously - returning the first match. It does - this by creating a huge (a|b|c) regex - each individual item wrapped with () - and joined by `|` - using match groups to track position. When a match is - found checking which position in the array has content allows us to figure - out which of the original regexes / match groups triggered the match. - - The match object itself (the result of `Regex.exec`) is returned but also - enhanced by merging in any meta-data that was registered with the regex. - This is how we keep track of which mode matched, and what type of rule - (`illegal`, `begin`, end, etc). - */ - class MultiRegex { - constructor() { - this.matchIndexes = {}; - // @ts-ignore - this.regexes = []; - this.matchAt = 1; - this.position = 0; - } - - // @ts-ignore - addRule(re, opts) { - opts.position = this.position++; - // @ts-ignore - this.matchIndexes[this.matchAt] = opts; - this.regexes.push([opts, re]); - this.matchAt += countMatchGroups(re) + 1; - } - - compile() { - if (this.regexes.length === 0) { - // avoids the need to check length every time exec is called - // @ts-ignore - this.exec = () => null; - } - const terminators = this.regexes.map(el => el[1]); - this.matcherRe = langRe(_rewriteBackreferences(terminators, { joinWith: '|' }), true); - this.lastIndex = 0; - } - - /** @param {string} s */ - exec(s) { - this.matcherRe.lastIndex = this.lastIndex; - const match = this.matcherRe.exec(s); - if (!match) { return null; } - - // eslint-disable-next-line no-undefined - const i = match.findIndex((el, i) => i > 0 && el !== undefined); - // @ts-ignore - const matchData = this.matchIndexes[i]; - // trim off any earlier non-relevant match groups (ie, the other regex - // match groups that make up the multi-matcher) - match.splice(0, i); - - return Object.assign(match, matchData); - } - } - - /* - Created to solve the key deficiently with MultiRegex - there is no way to - test for multiple matches at a single location. Why would we need to do - that? In the future a more dynamic engine will allow certain matches to be - ignored. An example: if we matched say the 3rd regex in a large group but - decided to ignore it - we'd need to started testing again at the 4th - regex... but MultiRegex itself gives us no real way to do that. - - So what this class creates MultiRegexs on the fly for whatever search - position they are needed. - - NOTE: These additional MultiRegex objects are created dynamically. For most - grammars most of the time we will never actually need anything more than the - first MultiRegex - so this shouldn't have too much overhead. - - Say this is our search group, and we match regex3, but wish to ignore it. - - regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0 - - What we need is a new MultiRegex that only includes the remaining - possibilities: - - regex4 | regex5 ' ie, startAt = 3 - - This class wraps all that complexity up in a simple API... `startAt` decides - where in the array of expressions to start doing the matching. It - auto-increments, so if a match is found at position 2, then startAt will be - set to 3. If the end is reached startAt will return to 0. - - MOST of the time the parser will be setting startAt manually to 0. - */ - class ResumableMultiRegex { - constructor() { - // @ts-ignore - this.rules = []; - // @ts-ignore - this.multiRegexes = []; - this.count = 0; - - this.lastIndex = 0; - this.regexIndex = 0; - } - - // @ts-ignore - getMatcher(index) { - if (this.multiRegexes[index]) return this.multiRegexes[index]; - - const matcher = new MultiRegex(); - this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts)); - matcher.compile(); - this.multiRegexes[index] = matcher; - return matcher; - } - - resumingScanAtSamePosition() { - return this.regexIndex !== 0; - } - - considerAll() { - this.regexIndex = 0; - } - - // @ts-ignore - addRule(re, opts) { - this.rules.push([re, opts]); - if (opts.type === "begin") this.count++; - } - - /** @param {string} s */ - exec(s) { - const m = this.getMatcher(this.regexIndex); - m.lastIndex = this.lastIndex; - let result = m.exec(s); - - // The following is because we have no easy way to say "resume scanning at the - // existing position but also skip the current rule ONLY". What happens is - // all prior rules are also skipped which can result in matching the wrong - // thing. Example of matching "booger": - - // our matcher is [string, "booger", number] - // - // ....booger.... - - // if "booger" is ignored then we'd really need a regex to scan from the - // SAME position for only: [string, number] but ignoring "booger" (if it - // was the first match), a simple resume would scan ahead who knows how - // far looking only for "number", ignoring potential string matches (or - // future "booger" matches that might be valid.) - - // So what we do: We execute two matchers, one resuming at the same - // position, but the second full matcher starting at the position after: - - // /--- resume first regex match here (for [number]) - // |/---- full match here for [string, "booger", number] - // vv - // ....booger.... - - // Which ever results in a match first is then used. So this 3-4 step - // process essentially allows us to say "match at this position, excluding - // a prior rule that was ignored". - // - // 1. Match "booger" first, ignore. Also proves that [string] does non match. - // 2. Resume matching for [number] - // 3. Match at index + 1 for [string, "booger", number] - // 4. If #2 and #3 result in matches, which came first? - if (this.resumingScanAtSamePosition()) { - if (result && result.index === this.lastIndex) ; else { // use the second matcher result - const m2 = this.getMatcher(0); - m2.lastIndex = this.lastIndex + 1; - result = m2.exec(s); - } - } - - if (result) { - this.regexIndex += result.position + 1; - if (this.regexIndex === this.count) { - // wrap-around to considering all matches again - this.considerAll(); - } - } - - return result; - } - } - - /** - * Given a mode, builds a huge ResumableMultiRegex that can be used to walk - * the content and find matches. - * - * @param {CompiledMode} mode - * @returns {ResumableMultiRegex} - */ - function buildModeRegex(mode) { - const mm = new ResumableMultiRegex(); - - mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: "begin" })); - - if (mode.terminatorEnd) { - mm.addRule(mode.terminatorEnd, { type: "end" }); - } - if (mode.illegal) { - mm.addRule(mode.illegal, { type: "illegal" }); - } - - return mm; - } - - /** skip vs abort vs ignore - * - * @skip - The mode is still entered and exited normally (and contains rules apply), - * but all content is held and added to the parent buffer rather than being - * output when the mode ends. Mostly used with `sublanguage` to build up - * a single large buffer than can be parsed by sublanguage. - * - * - The mode begin ands ends normally. - * - Content matched is added to the parent mode buffer. - * - The parser cursor is moved forward normally. - * - * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it - * never matched) but DOES NOT continue to match subsequent `contains` - * modes. Abort is bad/suboptimal because it can result in modes - * farther down not getting applied because an earlier rule eats the - * content but then aborts. - * - * - The mode does not begin. - * - Content matched by `begin` is added to the mode buffer. - * - The parser cursor is moved forward accordingly. - * - * @ignore - Ignores the mode (as if it never matched) and continues to match any - * subsequent `contains` modes. Ignore isn't technically possible with - * the current parser implementation. - * - * - The mode does not begin. - * - Content matched by `begin` is ignored. - * - The parser cursor is not moved forward. - */ - - /** - * Compiles an individual mode - * - * This can raise an error if the mode contains certain detectable known logic - * issues. - * @param {Mode} mode - * @param {CompiledMode | null} [parent] - * @returns {CompiledMode | never} - */ - function compileMode(mode, parent) { - const cmode = /** @type CompiledMode */ (mode); - if (mode.isCompiled) return cmode; - - [ - scopeClassName, - // do this early so compiler extensions generally don't have to worry about - // the distinction between match/begin - compileMatch, - MultiClass, - beforeMatchExt - ].forEach(ext => ext(mode, parent)); - - language.compilerExtensions.forEach(ext => ext(mode, parent)); - - // __beforeBegin is considered private API, internal use only - mode.__beforeBegin = null; - - [ - beginKeywords, - // do this later so compiler extensions that come earlier have access to the - // raw array if they wanted to perhaps manipulate it, etc. - compileIllegal, - // default to 1 relevance if not specified - compileRelevance - ].forEach(ext => ext(mode, parent)); - - mode.isCompiled = true; - - let keywordPattern = null; - if (typeof mode.keywords === "object" && mode.keywords.$pattern) { - // we need a copy because keywords might be compiled multiple times - // so we can't go deleting $pattern from the original on the first - // pass - mode.keywords = Object.assign({}, mode.keywords); - keywordPattern = mode.keywords.$pattern; - delete mode.keywords.$pattern; - } - keywordPattern = keywordPattern || /\w+/; - - if (mode.keywords) { - mode.keywords = compileKeywords(mode.keywords, language.case_insensitive); - } - - cmode.keywordPatternRe = langRe(keywordPattern, true); - - if (parent) { - if (!mode.begin) mode.begin = /\B|\b/; - cmode.beginRe = langRe(cmode.begin); - if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/; - if (mode.end) cmode.endRe = langRe(cmode.end); - cmode.terminatorEnd = source(cmode.end) || ''; - if (mode.endsWithParent && parent.terminatorEnd) { - cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd; - } - } - if (mode.illegal) cmode.illegalRe = langRe(/** @type {RegExp | string} */ (mode.illegal)); - if (!mode.contains) mode.contains = []; - - mode.contains = [].concat(...mode.contains.map(function(c) { - return expandOrCloneMode(c === 'self' ? mode : c); - })); - mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); }); - - if (mode.starts) { - compileMode(mode.starts, parent); - } - - cmode.matcher = buildModeRegex(cmode); - return cmode; - } - - if (!language.compilerExtensions) language.compilerExtensions = []; - - // self is not valid at the top-level - if (language.contains && language.contains.includes('self')) { - throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation."); - } - - // we need a null object, which inherit will guarantee - language.classNameAliases = inherit$1(language.classNameAliases || {}); - - return compileMode(/** @type Mode */ (language)); -} - -/** - * Determines if a mode has a dependency on it's parent or not - * - * If a mode does have a parent dependency then often we need to clone it if - * it's used in multiple places so that each copy points to the correct parent, - * where-as modes without a parent can often safely be re-used at the bottom of - * a mode chain. - * - * @param {Mode | null} mode - * @returns {boolean} - is there a dependency on the parent? - * */ -function dependencyOnParent(mode) { - if (!mode) return false; - - return mode.endsWithParent || dependencyOnParent(mode.starts); -} - -/** - * Expands a mode or clones it if necessary - * - * This is necessary for modes with parental dependenceis (see notes on - * `dependencyOnParent`) and for nodes that have `variants` - which must then be - * exploded into their own individual modes at compile time. - * - * @param {Mode} mode - * @returns {Mode | Mode[]} - * */ -function expandOrCloneMode(mode) { - if (mode.variants && !mode.cachedVariants) { - mode.cachedVariants = mode.variants.map(function(variant) { - return inherit$1(mode, { variants: null }, variant); - }); - } - - // EXPAND - // if we have variants then essentially "replace" the mode with the variants - // this happens in compileMode, where this function is called from - if (mode.cachedVariants) { - return mode.cachedVariants; - } - - // CLONE - // if we have dependencies on parents then we need a unique - // instance of ourselves, so we can be reused with many - // different parents without issue - if (dependencyOnParent(mode)) { - return inherit$1(mode, { starts: mode.starts ? inherit$1(mode.starts) : null }); - } - - if (Object.isFrozen(mode)) { - return inherit$1(mode); - } - - // no special dependency issues, just return ourselves - return mode; -} - -var version = "11.6.0"; - -class HTMLInjectionError extends Error { - constructor(reason, html) { - super(reason); - this.name = "HTMLInjectionError"; - this.html = html; - } -} - -/* -Syntax highlighting with language autodetection. -https://highlightjs.org/ -*/ - -/** -@typedef {import('highlight.js').Mode} Mode -@typedef {import('highlight.js').CompiledMode} CompiledMode -@typedef {import('highlight.js').CompiledScope} CompiledScope -@typedef {import('highlight.js').Language} Language -@typedef {import('highlight.js').HLJSApi} HLJSApi -@typedef {import('highlight.js').HLJSPlugin} HLJSPlugin -@typedef {import('highlight.js').PluginEvent} PluginEvent -@typedef {import('highlight.js').HLJSOptions} HLJSOptions -@typedef {import('highlight.js').LanguageFn} LanguageFn -@typedef {import('highlight.js').HighlightedHTMLElement} HighlightedHTMLElement -@typedef {import('highlight.js').BeforeHighlightContext} BeforeHighlightContext -@typedef {import('highlight.js/private').MatchType} MatchType -@typedef {import('highlight.js/private').KeywordData} KeywordData -@typedef {import('highlight.js/private').EnhancedMatch} EnhancedMatch -@typedef {import('highlight.js/private').AnnotatedError} AnnotatedError -@typedef {import('highlight.js').AutoHighlightResult} AutoHighlightResult -@typedef {import('highlight.js').HighlightOptions} HighlightOptions -@typedef {import('highlight.js').HighlightResult} HighlightResult -*/ - - -const escape = escapeHTML; -const inherit = inherit$1; -const NO_MATCH = Symbol("nomatch"); -const MAX_KEYWORD_HITS = 7; - -/** - * @param {any} hljs - object that is extended (legacy) - * @returns {HLJSApi} - */ -const HLJS = function(hljs) { - // Global internal variables used within the highlight.js library. - /** @type {Record} */ - const languages = Object.create(null); - /** @type {Record} */ - const aliases = Object.create(null); - /** @type {HLJSPlugin[]} */ - const plugins = []; - - // safe/production mode - swallows more errors, tries to keep running - // even if a single syntax or parse hits a fatal error - let SAFE_MODE = true; - const LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?"; - /** @type {Language} */ - const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] }; - - // Global options used when within external APIs. This is modified when - // calling the `hljs.configure` function. - /** @type HLJSOptions */ - let options = { - ignoreUnescapedHTML: false, - throwUnescapedHTML: false, - noHighlightRe: /^(no-?highlight)$/i, - languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, - classPrefix: 'hljs-', - cssSelector: 'pre code', - languages: null, - // beta configuration options, subject to change, welcome to discuss - // https://github.com/highlightjs/highlight.js/issues/1086 - __emitter: TokenTreeEmitter - }; - - /* Utility functions */ - - /** - * Tests a language name to see if highlighting should be skipped - * @param {string} languageName - */ - function shouldNotHighlight(languageName) { - return options.noHighlightRe.test(languageName); - } - - /** - * @param {HighlightedHTMLElement} block - the HTML element to determine language for - */ - function blockLanguage(block) { - let classes = block.className + ' '; - - classes += block.parentNode ? block.parentNode.className : ''; - - // language-* takes precedence over non-prefixed class names. - const match = options.languageDetectRe.exec(classes); - if (match) { - const language = getLanguage(match[1]); - if (!language) { - warn(LANGUAGE_NOT_FOUND.replace("{}", match[1])); - warn("Falling back to no-highlight mode for this block.", block); - } - return language ? match[1] : 'no-highlight'; - } - - return classes - .split(/\s+/) - .find((_class) => shouldNotHighlight(_class) || getLanguage(_class)); - } - - /** - * Core highlighting function. - * - * OLD API - * highlight(lang, code, ignoreIllegals, continuation) - * - * NEW API - * highlight(code, {lang, ignoreIllegals}) - * - * @param {string} codeOrLanguageName - the language to use for highlighting - * @param {string | HighlightOptions} optionsOrCode - the code to highlight - * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail - * - * @returns {HighlightResult} Result - an object that represents the result - * @property {string} language - the language name - * @property {number} relevance - the relevance score - * @property {string} value - the highlighted HTML code - * @property {string} code - the original raw code - * @property {CompiledMode} top - top of the current mode stack - * @property {boolean} illegal - indicates whether any illegal matches were found - */ - function highlight(codeOrLanguageName, optionsOrCode, ignoreIllegals) { - let code = ""; - let languageName = ""; - if (typeof optionsOrCode === "object") { - code = codeOrLanguageName; - ignoreIllegals = optionsOrCode.ignoreIllegals; - languageName = optionsOrCode.language; - } else { - // old API - deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated."); - deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"); - languageName = codeOrLanguageName; - code = optionsOrCode; - } - - // https://github.com/highlightjs/highlight.js/issues/3149 - // eslint-disable-next-line no-undefined - if (ignoreIllegals === undefined) { ignoreIllegals = true; } - - /** @type {BeforeHighlightContext} */ - const context = { - code, - language: languageName - }; - // the plugin can change the desired language or the code to be highlighted - // just be changing the object it was passed - fire("before:highlight", context); - - // a before plugin can usurp the result completely by providing it's own - // in which case we don't even need to call highlight - const result = context.result - ? context.result - : _highlight(context.language, context.code, ignoreIllegals); - - result.code = context.code; - // the plugin can change anything in result to suite it - fire("after:highlight", result); - - return result; - } - - /** - * private highlight that's used internally and does not fire callbacks - * - * @param {string} languageName - the language to use for highlighting - * @param {string} codeToHighlight - the code to highlight - * @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail - * @param {CompiledMode?} [continuation] - current continuation mode, if any - * @returns {HighlightResult} - result of the highlight operation - */ - function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) { - const keywordHits = Object.create(null); - - /** - * Return keyword data if a match is a keyword - * @param {CompiledMode} mode - current mode - * @param {string} matchText - the textual match - * @returns {KeywordData | false} - */ - function keywordData(mode, matchText) { - return mode.keywords[matchText]; - } - - function processKeywords() { - if (!top.keywords) { - emitter.addText(modeBuffer); - return; - } - - let lastIndex = 0; - top.keywordPatternRe.lastIndex = 0; - let match = top.keywordPatternRe.exec(modeBuffer); - let buf = ""; - - while (match) { - buf += modeBuffer.substring(lastIndex, match.index); - const word = language.case_insensitive ? match[0].toLowerCase() : match[0]; - const data = keywordData(top, word); - if (data) { - const [kind, keywordRelevance] = data; - emitter.addText(buf); - buf = ""; - - keywordHits[word] = (keywordHits[word] || 0) + 1; - if (keywordHits[word] <= MAX_KEYWORD_HITS) relevance += keywordRelevance; - if (kind.startsWith("_")) { - // _ implied for relevance only, do not highlight - // by applying a class name - buf += match[0]; - } else { - const cssClass = language.classNameAliases[kind] || kind; - emitter.addKeyword(match[0], cssClass); - } - } else { - buf += match[0]; - } - lastIndex = top.keywordPatternRe.lastIndex; - match = top.keywordPatternRe.exec(modeBuffer); - } - buf += modeBuffer.substring(lastIndex); - emitter.addText(buf); - } - - function processSubLanguage() { - if (modeBuffer === "") return; - /** @type HighlightResult */ - let result = null; - - if (typeof top.subLanguage === 'string') { - if (!languages[top.subLanguage]) { - emitter.addText(modeBuffer); - return; - } - result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]); - continuations[top.subLanguage] = /** @type {CompiledMode} */ (result._top); - } else { - result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null); - } - - // Counting embedded language score towards the host language may be disabled - // with zeroing the containing mode relevance. Use case in point is Markdown that - // allows XML everywhere and makes every XML snippet to have a much larger Markdown - // score. - if (top.relevance > 0) { - relevance += result.relevance; - } - emitter.addSublanguage(result._emitter, result.language); - } - - function processBuffer() { - if (top.subLanguage != null) { - processSubLanguage(); - } else { - processKeywords(); - } - modeBuffer = ''; - } - - /** - * @param {CompiledScope} scope - * @param {RegExpMatchArray} match - */ - function emitMultiClass(scope, match) { - let i = 1; - const max = match.length - 1; - while (i <= max) { - if (!scope._emit[i]) { i++; continue; } - const klass = language.classNameAliases[scope[i]] || scope[i]; - const text = match[i]; - if (klass) { - emitter.addKeyword(text, klass); - } else { - modeBuffer = text; - processKeywords(); - modeBuffer = ""; - } - i++; - } - } - - /** - * @param {CompiledMode} mode - new mode to start - * @param {RegExpMatchArray} match - */ - function startNewMode(mode, match) { - if (mode.scope && typeof mode.scope === "string") { - emitter.openNode(language.classNameAliases[mode.scope] || mode.scope); - } - if (mode.beginScope) { - // beginScope just wraps the begin match itself in a scope - if (mode.beginScope._wrap) { - emitter.addKeyword(modeBuffer, language.classNameAliases[mode.beginScope._wrap] || mode.beginScope._wrap); - modeBuffer = ""; - } else if (mode.beginScope._multi) { - // at this point modeBuffer should just be the match - emitMultiClass(mode.beginScope, match); - modeBuffer = ""; - } - } - - top = Object.create(mode, { parent: { value: top } }); - return top; - } - - /** - * @param {CompiledMode } mode - the mode to potentially end - * @param {RegExpMatchArray} match - the latest match - * @param {string} matchPlusRemainder - match plus remainder of content - * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode - */ - function endOfMode(mode, match, matchPlusRemainder) { - let matched = startsWith(mode.endRe, matchPlusRemainder); - - if (matched) { - if (mode["on:end"]) { - const resp = new Response(mode); - mode["on:end"](match, resp); - if (resp.isMatchIgnored) matched = false; - } - - if (matched) { - while (mode.endsParent && mode.parent) { - mode = mode.parent; - } - return mode; - } - } - // even if on:end fires an `ignore` it's still possible - // that we might trigger the end node because of a parent mode - if (mode.endsWithParent) { - return endOfMode(mode.parent, match, matchPlusRemainder); - } - } - - /** - * Handle matching but then ignoring a sequence of text - * - * @param {string} lexeme - string containing full match text - */ - function doIgnore(lexeme) { - if (top.matcher.regexIndex === 0) { - // no more regexes to potentially match here, so we move the cursor forward one - // space - modeBuffer += lexeme[0]; - return 1; - } else { - // no need to move the cursor, we still have additional regexes to try and - // match at this very spot - resumeScanAtSamePosition = true; - return 0; - } - } - - /** - * Handle the start of a new potential mode match - * - * @param {EnhancedMatch} match - the current match - * @returns {number} how far to advance the parse cursor - */ - function doBeginMatch(match) { - const lexeme = match[0]; - const newMode = match.rule; - - const resp = new Response(newMode); - // first internal before callbacks, then the public ones - const beforeCallbacks = [newMode.__beforeBegin, newMode["on:begin"]]; - for (const cb of beforeCallbacks) { - if (!cb) continue; - cb(match, resp); - if (resp.isMatchIgnored) return doIgnore(lexeme); - } - - if (newMode.skip) { - modeBuffer += lexeme; - } else { - if (newMode.excludeBegin) { - modeBuffer += lexeme; - } - processBuffer(); - if (!newMode.returnBegin && !newMode.excludeBegin) { - modeBuffer = lexeme; - } - } - startNewMode(newMode, match); - return newMode.returnBegin ? 0 : lexeme.length; - } - - /** - * Handle the potential end of mode - * - * @param {RegExpMatchArray} match - the current match - */ - function doEndMatch(match) { - const lexeme = match[0]; - const matchPlusRemainder = codeToHighlight.substring(match.index); - - const endMode = endOfMode(top, match, matchPlusRemainder); - if (!endMode) { return NO_MATCH; } - - const origin = top; - if (top.endScope && top.endScope._wrap) { - processBuffer(); - emitter.addKeyword(lexeme, top.endScope._wrap); - } else if (top.endScope && top.endScope._multi) { - processBuffer(); - emitMultiClass(top.endScope, match); - } else if (origin.skip) { - modeBuffer += lexeme; - } else { - if (!(origin.returnEnd || origin.excludeEnd)) { - modeBuffer += lexeme; - } - processBuffer(); - if (origin.excludeEnd) { - modeBuffer = lexeme; - } - } - do { - if (top.scope) { - emitter.closeNode(); - } - if (!top.skip && !top.subLanguage) { - relevance += top.relevance; - } - top = top.parent; - } while (top !== endMode.parent); - if (endMode.starts) { - startNewMode(endMode.starts, match); - } - return origin.returnEnd ? 0 : lexeme.length; - } - - function processContinuations() { - const list = []; - for (let current = top; current !== language; current = current.parent) { - if (current.scope) { - list.unshift(current.scope); - } - } - list.forEach(item => emitter.openNode(item)); - } - - /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */ - let lastMatch = {}; - - /** - * Process an individual match - * - * @param {string} textBeforeMatch - text preceding the match (since the last match) - * @param {EnhancedMatch} [match] - the match itself - */ - function processLexeme(textBeforeMatch, match) { - const lexeme = match && match[0]; - - // add non-matched text to the current mode buffer - modeBuffer += textBeforeMatch; - - if (lexeme == null) { - processBuffer(); - return 0; - } - - // we've found a 0 width match and we're stuck, so we need to advance - // this happens when we have badly behaved rules that have optional matchers to the degree that - // sometimes they can end up matching nothing at all - // Ref: https://github.com/highlightjs/highlight.js/issues/2140 - if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") { - // spit the "skipped" character that our regex choked on back into the output sequence - modeBuffer += codeToHighlight.slice(match.index, match.index + 1); - if (!SAFE_MODE) { - /** @type {AnnotatedError} */ - const err = new Error(`0 width match regex (${languageName})`); - err.languageName = languageName; - err.badRule = lastMatch.rule; - throw err; - } - return 1; - } - lastMatch = match; - - if (match.type === "begin") { - return doBeginMatch(match); - } else if (match.type === "illegal" && !ignoreIllegals) { - // illegal match, we do not continue processing - /** @type {AnnotatedError} */ - const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.scope || '') + '"'); - err.mode = top; - throw err; - } else if (match.type === "end") { - const processed = doEndMatch(match); - if (processed !== NO_MATCH) { - return processed; - } - } - - // edge case for when illegal matches $ (end of line) which is technically - // a 0 width match but not a begin/end match so it's not caught by the - // first handler (when ignoreIllegals is true) - if (match.type === "illegal" && lexeme === "") { - // advance so we aren't stuck in an infinite loop - return 1; - } - - // infinite loops are BAD, this is a last ditch catch all. if we have a - // decent number of iterations yet our index (cursor position in our - // parsing) still 3x behind our index then something is very wrong - // so we bail - if (iterations > 100000 && iterations > match.index * 3) { - const err = new Error('potential infinite loop, way more iterations than matches'); - throw err; - } - - /* - Why might be find ourselves here? An potential end match that was - triggered but could not be completed. IE, `doEndMatch` returned NO_MATCH. - (this could be because a callback requests the match be ignored, etc) - - This causes no real harm other than stopping a few times too many. - */ - - modeBuffer += lexeme; - return lexeme.length; - } - - const language = getLanguage(languageName); - if (!language) { - error(LANGUAGE_NOT_FOUND.replace("{}", languageName)); - throw new Error('Unknown language: "' + languageName + '"'); - } - - const md = compileLanguage(language); - let result = ''; - /** @type {CompiledMode} */ - let top = continuation || md; - /** @type Record */ - const continuations = {}; // keep continuations for sub-languages - const emitter = new options.__emitter(options); - processContinuations(); - let modeBuffer = ''; - let relevance = 0; - let index = 0; - let iterations = 0; - let resumeScanAtSamePosition = false; - - try { - top.matcher.considerAll(); - - for (;;) { - iterations++; - if (resumeScanAtSamePosition) { - // only regexes not matched previously will now be - // considered for a potential match - resumeScanAtSamePosition = false; - } else { - top.matcher.considerAll(); - } - top.matcher.lastIndex = index; - - const match = top.matcher.exec(codeToHighlight); - // console.log("match", match[0], match.rule && match.rule.begin) - - if (!match) break; - - const beforeMatch = codeToHighlight.substring(index, match.index); - const processedCount = processLexeme(beforeMatch, match); - index = match.index + processedCount; - } - processLexeme(codeToHighlight.substring(index)); - emitter.closeAllNodes(); - emitter.finalize(); - result = emitter.toHTML(); - - return { - language: languageName, - value: result, - relevance: relevance, - illegal: false, - _emitter: emitter, - _top: top - }; - } catch (err) { - if (err.message && err.message.includes('Illegal')) { - return { - language: languageName, - value: escape(codeToHighlight), - illegal: true, - relevance: 0, - _illegalBy: { - message: err.message, - index: index, - context: codeToHighlight.slice(index - 100, index + 100), - mode: err.mode, - resultSoFar: result - }, - _emitter: emitter - }; - } else if (SAFE_MODE) { - return { - language: languageName, - value: escape(codeToHighlight), - illegal: false, - relevance: 0, - errorRaised: err, - _emitter: emitter, - _top: top - }; - } else { - throw err; - } - } - } - - /** - * returns a valid highlight result, without actually doing any actual work, - * auto highlight starts with this and it's possible for small snippets that - * auto-detection may not find a better match - * @param {string} code - * @returns {HighlightResult} - */ - function justTextHighlightResult(code) { - const result = { - value: escape(code), - illegal: false, - relevance: 0, - _top: PLAINTEXT_LANGUAGE, - _emitter: new options.__emitter(options) - }; - result._emitter.addText(code); - return result; - } - - /** - Highlighting with language detection. Accepts a string with the code to - highlight. Returns an object with the following properties: - - - language (detected language) - - relevance (int) - - value (an HTML string with highlighting markup) - - secondBest (object with the same structure for second-best heuristically - detected language, may be absent) - - @param {string} code - @param {Array} [languageSubset] - @returns {AutoHighlightResult} - */ - function highlightAuto(code, languageSubset) { - languageSubset = languageSubset || options.languages || Object.keys(languages); - const plaintext = justTextHighlightResult(code); - - const results = languageSubset.filter(getLanguage).filter(autoDetection).map(name => - _highlight(name, code, false) - ); - results.unshift(plaintext); // plaintext is always an option - - const sorted = results.sort((a, b) => { - // sort base on relevance - if (a.relevance !== b.relevance) return b.relevance - a.relevance; - - // always award the tie to the base language - // ie if C++ and Arduino are tied, it's more likely to be C++ - if (a.language && b.language) { - if (getLanguage(a.language).supersetOf === b.language) { - return 1; - } else if (getLanguage(b.language).supersetOf === a.language) { - return -1; - } - } - - // otherwise say they are equal, which has the effect of sorting on - // relevance while preserving the original ordering - which is how ties - // have historically been settled, ie the language that comes first always - // wins in the case of a tie - return 0; - }); - - const [best, secondBest] = sorted; - - /** @type {AutoHighlightResult} */ - const result = best; - result.secondBest = secondBest; - - return result; - } - - /** - * Builds new class name for block given the language name - * - * @param {HTMLElement} element - * @param {string} [currentLang] - * @param {string} [resultLang] - */ - function updateClassName(element, currentLang, resultLang) { - const language = (currentLang && aliases[currentLang]) || resultLang; - - element.classList.add("hljs"); - element.classList.add(`language-${language}`); - } - - /** - * Applies highlighting to a DOM node containing code. - * - * @param {HighlightedHTMLElement} element - the HTML element to highlight - */ - function highlightElement(element) { - /** @type HTMLElement */ - let node = null; - const language = blockLanguage(element); - - if (shouldNotHighlight(language)) return; - - fire("before:highlightElement", - { el: element, language: language }); - - // we should be all text, no child nodes (unescaped HTML) - this is possibly - // an HTML injection attack - it's likely too late if this is already in - // production (the code has likely already done its damage by the time - // we're seeing it)... but we yell loudly about this so that hopefully it's - // more likely to be caught in development before making it to production - if (element.children.length > 0) { - if (!options.ignoreUnescapedHTML) { - console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."); - console.warn("https://github.com/highlightjs/highlight.js/wiki/security"); - console.warn("The element with unescaped HTML:"); - console.warn(element); - } - if (options.throwUnescapedHTML) { - const err = new HTMLInjectionError( - "One of your code blocks includes unescaped HTML.", - element.innerHTML - ); - throw err; - } - } - - node = element; - const text = node.textContent; - const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text); - - element.innerHTML = result.value; - updateClassName(element, language, result.language); - element.result = { - language: result.language, - // TODO: remove with version 11.0 - re: result.relevance, - relevance: result.relevance - }; - if (result.secondBest) { - element.secondBest = { - language: result.secondBest.language, - relevance: result.secondBest.relevance - }; - } - - fire("after:highlightElement", { el: element, result, text }); - } - - /** - * Updates highlight.js global options with the passed options - * - * @param {Partial} userOptions - */ - function configure(userOptions) { - options = inherit(options, userOptions); - } - - // TODO: remove v12, deprecated - const initHighlighting = () => { - highlightAll(); - deprecated("10.6.0", "initHighlighting() deprecated. Use highlightAll() now."); - }; - - // TODO: remove v12, deprecated - function initHighlightingOnLoad() { - highlightAll(); - deprecated("10.6.0", "initHighlightingOnLoad() deprecated. Use highlightAll() now."); - } - - let wantsHighlight = false; - - /** - * auto-highlights all pre>code elements on the page - */ - function highlightAll() { - // if we are called too early in the loading process - if (document.readyState === "loading") { - wantsHighlight = true; - return; - } - - const blocks = document.querySelectorAll(options.cssSelector); - blocks.forEach(highlightElement); - } - - function boot() { - // if a highlight was requested before DOM was loaded, do now - if (wantsHighlight) highlightAll(); - } - - // make sure we are in the browser environment - if (typeof window !== 'undefined' && window.addEventListener) { - window.addEventListener('DOMContentLoaded', boot, false); - } - - /** - * Register a language grammar module - * - * @param {string} languageName - * @param {LanguageFn} languageDefinition - */ - function registerLanguage(languageName, languageDefinition) { - let lang = null; - try { - lang = languageDefinition(hljs); - } catch (error$1) { - error("Language definition for '{}' could not be registered.".replace("{}", languageName)); - // hard or soft error - if (!SAFE_MODE) { throw error$1; } else { error(error$1); } - // languages that have serious errors are replaced with essentially a - // "plaintext" stand-in so that the code blocks will still get normal - // css classes applied to them - and one bad language won't break the - // entire highlighter - lang = PLAINTEXT_LANGUAGE; - } - // give it a temporary name if it doesn't have one in the meta-data - if (!lang.name) lang.name = languageName; - languages[languageName] = lang; - lang.rawDefinition = languageDefinition.bind(null, hljs); - - if (lang.aliases) { - registerAliases(lang.aliases, { languageName }); - } - } - - /** - * Remove a language grammar module - * - * @param {string} languageName - */ - function unregisterLanguage(languageName) { - delete languages[languageName]; - for (const alias of Object.keys(aliases)) { - if (aliases[alias] === languageName) { - delete aliases[alias]; - } - } - } - - /** - * @returns {string[]} List of language internal names - */ - function listLanguages() { - return Object.keys(languages); - } - - /** - * @param {string} name - name of the language to retrieve - * @returns {Language | undefined} - */ - function getLanguage(name) { - name = (name || '').toLowerCase(); - return languages[name] || languages[aliases[name]]; - } - - /** - * - * @param {string|string[]} aliasList - single alias or list of aliases - * @param {{languageName: string}} opts - */ - function registerAliases(aliasList, { languageName }) { - if (typeof aliasList === 'string') { - aliasList = [aliasList]; - } - aliasList.forEach(alias => { aliases[alias.toLowerCase()] = languageName; }); - } - - /** - * Determines if a given language has auto-detection enabled - * @param {string} name - name of the language - */ - function autoDetection(name) { - const lang = getLanguage(name); - return lang && !lang.disableAutodetect; - } - - /** - * Upgrades the old highlightBlock plugins to the new - * highlightElement API - * @param {HLJSPlugin} plugin - */ - function upgradePluginAPI(plugin) { - // TODO: remove with v12 - if (plugin["before:highlightBlock"] && !plugin["before:highlightElement"]) { - plugin["before:highlightElement"] = (data) => { - plugin["before:highlightBlock"]( - Object.assign({ block: data.el }, data) - ); - }; - } - if (plugin["after:highlightBlock"] && !plugin["after:highlightElement"]) { - plugin["after:highlightElement"] = (data) => { - plugin["after:highlightBlock"]( - Object.assign({ block: data.el }, data) - ); - }; - } - } - - /** - * @param {HLJSPlugin} plugin - */ - function addPlugin(plugin) { - upgradePluginAPI(plugin); - plugins.push(plugin); - } - - /** - * - * @param {PluginEvent} event - * @param {any} args - */ - function fire(event, args) { - const cb = event; - plugins.forEach(function(plugin) { - if (plugin[cb]) { - plugin[cb](args); - } - }); - } - - /** - * DEPRECATED - * @param {HighlightedHTMLElement} el - */ - function deprecateHighlightBlock(el) { - deprecated("10.7.0", "highlightBlock will be removed entirely in v12.0"); - deprecated("10.7.0", "Please use highlightElement now."); - - return highlightElement(el); - } - - /* Interface definition */ - Object.assign(hljs, { - highlight, - highlightAuto, - highlightAll, - highlightElement, - // TODO: Remove with v12 API - highlightBlock: deprecateHighlightBlock, - configure, - initHighlighting, - initHighlightingOnLoad, - registerLanguage, - unregisterLanguage, - listLanguages, - getLanguage, - registerAliases, - autoDetection, - inherit, - addPlugin - }); - - hljs.debugMode = function() { SAFE_MODE = false; }; - hljs.safeMode = function() { SAFE_MODE = true; }; - hljs.versionString = version; - - hljs.regex = { - concat: concat, - lookahead: lookahead, - either: either, - optional: optional, - anyNumberOfTimes: anyNumberOfTimes - }; - - for (const key in MODES) { - // @ts-ignore - if (typeof MODES[key] === "object") { - // @ts-ignore - deepFreezeEs6.exports(MODES[key]); - } - } - - // merge all the modes/regexes into our main object - Object.assign(hljs, MODES); - - return hljs; -}; - -// export an "instance" of the highlighter -export default HLJS({}); diff --git a/packages/web/src/third_party/highlightjs/highlightjs/github-dark.css b/packages/web/src/third_party/highlightjs/highlightjs/github-dark.css deleted file mode 100644 index 03b6da8bf4..0000000000 --- a/packages/web/src/third_party/highlightjs/highlightjs/github-dark.css +++ /dev/null @@ -1,10 +0,0 @@ -pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! - Theme: GitHub Dark - Description: Dark theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-dark - Current colors taken from GitHub's CSS -*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c} \ No newline at end of file diff --git a/packages/web/src/third_party/highlightjs/highlightjs/github.css b/packages/web/src/third_party/highlightjs/highlightjs/github.css deleted file mode 100644 index 275239a7aa..0000000000 --- a/packages/web/src/third_party/highlightjs/highlightjs/github.css +++ /dev/null @@ -1,10 +0,0 @@ -pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! - Theme: GitHub - Description: Light theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-light - Current colors taken from GitHub's CSS -*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0} \ No newline at end of file diff --git a/packages/web/src/third_party/highlightjs/highlightjs/index.js b/packages/web/src/third_party/highlightjs/highlightjs/index.js deleted file mode 100644 index 82a7cf373b..0000000000 --- a/packages/web/src/third_party/highlightjs/highlightjs/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import core from './core'; -import javascript from './languages/javascript'; -import python from './languages/python'; -import csharp from './languages/csharp'; -import java from './languages/java'; - -core.registerLanguage('javascript', javascript); -core.registerLanguage('python', python); -core.registerLanguage('csharp', csharp); -core.registerLanguage('java', java); - -export default core; diff --git a/packages/web/src/third_party/highlightjs/highlightjs/languages/csharp.js b/packages/web/src/third_party/highlightjs/highlightjs/languages/csharp.js deleted file mode 100644 index 894ce4d0f5..0000000000 --- a/packages/web/src/third_party/highlightjs/highlightjs/languages/csharp.js +++ /dev/null @@ -1,398 +0,0 @@ -/* -Language: C# -Author: Jason Diamond -Contributor: Nicolas LLOBERA , Pieter Vantorre , David Pine -Website: https://docs.microsoft.com/dotnet/csharp/ -Category: common -*/ - -/** @type LanguageFn */ -export default function csharp(hljs) { - const BUILT_IN_KEYWORDS = [ - 'bool', - 'byte', - 'char', - 'decimal', - 'delegate', - 'double', - 'dynamic', - 'enum', - 'float', - 'int', - 'long', - 'nint', - 'nuint', - 'object', - 'sbyte', - 'short', - 'string', - 'ulong', - 'uint', - 'ushort' - ]; - const FUNCTION_MODIFIERS = [ - 'public', - 'private', - 'protected', - 'static', - 'internal', - 'protected', - 'abstract', - 'async', - 'extern', - 'override', - 'unsafe', - 'virtual', - 'new', - 'sealed', - 'partial' - ]; - const LITERAL_KEYWORDS = [ - 'default', - 'false', - 'null', - 'true' - ]; - const NORMAL_KEYWORDS = [ - 'abstract', - 'as', - 'base', - 'break', - 'case', - 'catch', - 'class', - 'const', - 'continue', - 'do', - 'else', - 'event', - 'explicit', - 'extern', - 'finally', - 'fixed', - 'for', - 'foreach', - 'goto', - 'if', - 'implicit', - 'in', - 'interface', - 'internal', - 'is', - 'lock', - 'namespace', - 'new', - 'operator', - 'out', - 'override', - 'params', - 'private', - 'protected', - 'public', - 'readonly', - 'record', - 'ref', - 'return', - 'scoped', - 'sealed', - 'sizeof', - 'stackalloc', - 'static', - 'struct', - 'switch', - 'this', - 'throw', - 'try', - 'typeof', - 'unchecked', - 'unsafe', - 'using', - 'virtual', - 'void', - 'volatile', - 'while' - ]; - const CONTEXTUAL_KEYWORDS = [ - 'add', - 'alias', - 'and', - 'ascending', - 'async', - 'await', - 'by', - 'descending', - 'equals', - 'from', - 'get', - 'global', - 'group', - 'init', - 'into', - 'join', - 'let', - 'nameof', - 'not', - 'notnull', - 'on', - 'or', - 'orderby', - 'partial', - 'remove', - 'select', - 'set', - 'unmanaged', - 'value|0', - 'var', - 'when', - 'where', - 'with', - 'yield' - ]; - - const KEYWORDS = { - keyword: NORMAL_KEYWORDS.concat(CONTEXTUAL_KEYWORDS), - built_in: BUILT_IN_KEYWORDS, - literal: LITERAL_KEYWORDS - }; - const TITLE_MODE = hljs.inherit(hljs.TITLE_MODE, { begin: '[a-zA-Z](\\.?\\w)*' }); - const NUMBERS = { - className: 'number', - variants: [ - { begin: '\\b(0b[01\']+)' }, - { begin: '(-?)\\b([\\d\']+(\\.[\\d\']*)?|\\.[\\d\']+)(u|U|l|L|ul|UL|f|F|b|B)' }, - { begin: '(-?)(\\b0[xX][a-fA-F0-9\']+|(\\b[\\d\']+(\\.[\\d\']*)?|\\.[\\d\']+)([eE][-+]?[\\d\']+)?)' } - ], - relevance: 0 - }; - const VERBATIM_STRING = { - className: 'string', - begin: '@"', - end: '"', - contains: [ { begin: '""' } ] - }; - const VERBATIM_STRING_NO_LF = hljs.inherit(VERBATIM_STRING, { illegal: /\n/ }); - const SUBST = { - className: 'subst', - begin: /\{/, - end: /\}/, - keywords: KEYWORDS - }; - const SUBST_NO_LF = hljs.inherit(SUBST, { illegal: /\n/ }); - const INTERPOLATED_STRING = { - className: 'string', - begin: /\$"/, - end: '"', - illegal: /\n/, - contains: [ - { begin: /\{\{/ }, - { begin: /\}\}/ }, - hljs.BACKSLASH_ESCAPE, - SUBST_NO_LF - ] - }; - const INTERPOLATED_VERBATIM_STRING = { - className: 'string', - begin: /\$@"/, - end: '"', - contains: [ - { begin: /\{\{/ }, - { begin: /\}\}/ }, - { begin: '""' }, - SUBST - ] - }; - const INTERPOLATED_VERBATIM_STRING_NO_LF = hljs.inherit(INTERPOLATED_VERBATIM_STRING, { - illegal: /\n/, - contains: [ - { begin: /\{\{/ }, - { begin: /\}\}/ }, - { begin: '""' }, - SUBST_NO_LF - ] - }); - SUBST.contains = [ - INTERPOLATED_VERBATIM_STRING, - INTERPOLATED_STRING, - VERBATIM_STRING, - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE, - NUMBERS, - hljs.C_BLOCK_COMMENT_MODE - ]; - SUBST_NO_LF.contains = [ - INTERPOLATED_VERBATIM_STRING_NO_LF, - INTERPOLATED_STRING, - VERBATIM_STRING_NO_LF, - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE, - NUMBERS, - hljs.inherit(hljs.C_BLOCK_COMMENT_MODE, { illegal: /\n/ }) - ]; - const STRING = { variants: [ - INTERPOLATED_VERBATIM_STRING, - INTERPOLATED_STRING, - VERBATIM_STRING, - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE - ] }; - - const GENERIC_MODIFIER = { - begin: "<", - end: ">", - contains: [ - { beginKeywords: "in out" }, - TITLE_MODE - ] - }; - const TYPE_IDENT_RE = hljs.IDENT_RE + '(<' + hljs.IDENT_RE + '(\\s*,\\s*' + hljs.IDENT_RE + ')*>)?(\\[\\])?'; - const AT_IDENTIFIER = { - // prevents expressions like `@class` from incorrect flagging - // `class` as a keyword - begin: "@" + hljs.IDENT_RE, - relevance: 0 - }; - - return { - name: 'C#', - aliases: [ - 'cs', - 'c#' - ], - keywords: KEYWORDS, - illegal: /::/, - contains: [ - hljs.COMMENT( - '///', - '$', - { - returnBegin: true, - contains: [ - { - className: 'doctag', - variants: [ - { - begin: '///', - relevance: 0 - }, - { begin: '' }, - { - begin: '' - } - ] - } - ] - } - ), - hljs.C_LINE_COMMENT_MODE, - hljs.C_BLOCK_COMMENT_MODE, - { - className: 'meta', - begin: '#', - end: '$', - keywords: { keyword: 'if else elif endif define undef warning error line region endregion pragma checksum' } - }, - STRING, - NUMBERS, - { - beginKeywords: 'class interface', - relevance: 0, - end: /[{;=]/, - illegal: /[^\s:,]/, - contains: [ - { beginKeywords: "where class" }, - TITLE_MODE, - GENERIC_MODIFIER, - hljs.C_LINE_COMMENT_MODE, - hljs.C_BLOCK_COMMENT_MODE - ] - }, - { - beginKeywords: 'namespace', - relevance: 0, - end: /[{;=]/, - illegal: /[^\s:]/, - contains: [ - TITLE_MODE, - hljs.C_LINE_COMMENT_MODE, - hljs.C_BLOCK_COMMENT_MODE - ] - }, - { - beginKeywords: 'record', - relevance: 0, - end: /[{;=]/, - illegal: /[^\s:]/, - contains: [ - TITLE_MODE, - GENERIC_MODIFIER, - hljs.C_LINE_COMMENT_MODE, - hljs.C_BLOCK_COMMENT_MODE - ] - }, - { - // [Attributes("")] - className: 'meta', - begin: '^\\s*\\[(?=[\\w])', - excludeBegin: true, - end: '\\]', - excludeEnd: true, - contains: [ - { - className: 'string', - begin: /"/, - end: /"/ - } - ] - }, - { - // Expression keywords prevent 'keyword Name(...)' from being - // recognized as a function definition - beginKeywords: 'new return throw await else', - relevance: 0 - }, - { - className: 'function', - begin: '(' + TYPE_IDENT_RE + '\\s+)+' + hljs.IDENT_RE + '\\s*(<[^=]+>\\s*)?\\(', - returnBegin: true, - end: /\s*[{;=]/, - excludeEnd: true, - keywords: KEYWORDS, - contains: [ - // prevents these from being highlighted `title` - { - beginKeywords: FUNCTION_MODIFIERS.join(" "), - relevance: 0 - }, - { - begin: hljs.IDENT_RE + '\\s*(<[^=]+>\\s*)?\\(', - returnBegin: true, - contains: [ - hljs.TITLE_MODE, - GENERIC_MODIFIER - ], - relevance: 0 - }, - { match: /\(\)/ }, - { - className: 'params', - begin: /\(/, - end: /\)/, - excludeBegin: true, - excludeEnd: true, - keywords: KEYWORDS, - relevance: 0, - contains: [ - STRING, - NUMBERS, - hljs.C_BLOCK_COMMENT_MODE - ] - }, - hljs.C_LINE_COMMENT_MODE, - hljs.C_BLOCK_COMMENT_MODE - ] - }, - AT_IDENTIFIER - ] - }; -} diff --git a/packages/web/src/third_party/highlightjs/highlightjs/languages/java.js b/packages/web/src/third_party/highlightjs/highlightjs/languages/java.js deleted file mode 100644 index 44e8fa896b..0000000000 --- a/packages/web/src/third_party/highlightjs/highlightjs/languages/java.js +++ /dev/null @@ -1,284 +0,0 @@ -// https://docs.oracle.com/javase/specs/jls/se15/html/jls-3.html#jls-3.10 -var decimalDigits = '[0-9](_*[0-9])*'; -var frac = `\\.(${decimalDigits})`; -var hexDigits = '[0-9a-fA-F](_*[0-9a-fA-F])*'; -var NUMERIC = { - className: 'number', - variants: [ - // DecimalFloatingPointLiteral - // including ExponentPart - { begin: `(\\b(${decimalDigits})((${frac})|\\.)?|(${frac}))` + - `[eE][+-]?(${decimalDigits})[fFdD]?\\b` }, - // excluding ExponentPart - { begin: `\\b(${decimalDigits})((${frac})[fFdD]?\\b|\\.([fFdD]\\b)?)` }, - { begin: `(${frac})[fFdD]?\\b` }, - { begin: `\\b(${decimalDigits})[fFdD]\\b` }, - - // HexadecimalFloatingPointLiteral - { begin: `\\b0[xX]((${hexDigits})\\.?|(${hexDigits})?\\.(${hexDigits}))` + - `[pP][+-]?(${decimalDigits})[fFdD]?\\b` }, - - // DecimalIntegerLiteral - { begin: '\\b(0|[1-9](_*[0-9])*)[lL]?\\b' }, - - // HexIntegerLiteral - { begin: `\\b0[xX](${hexDigits})[lL]?\\b` }, - - // OctalIntegerLiteral - { begin: '\\b0(_*[0-7])*[lL]?\\b' }, - - // BinaryIntegerLiteral - { begin: '\\b0[bB][01](_*[01])*[lL]?\\b' }, - ], - relevance: 0 -}; - -/* -Language: Java -Author: Vsevolod Solovyov -Category: common, enterprise -Website: https://www.java.com/ -*/ - -/** - * Allows recursive regex expressions to a given depth - * - * ie: recurRegex("(abc~~~)", /~~~/g, 2) becomes: - * (abc(abc(abc))) - * - * @param {string} re - * @param {RegExp} substitution (should be a g mode regex) - * @param {number} depth - * @returns {string}`` - */ -function recurRegex(re, substitution, depth) { - if (depth === -1) return ""; - - return re.replace(substitution, _ => { - return recurRegex(re, substitution, depth - 1); - }); -} - -/** @type LanguageFn */ -export default function java(hljs) { - const regex = hljs.regex; - const JAVA_IDENT_RE = '[\u00C0-\u02B8a-zA-Z_$][\u00C0-\u02B8a-zA-Z_$0-9]*'; - const GENERIC_IDENT_RE = JAVA_IDENT_RE - + recurRegex('(?:<' + JAVA_IDENT_RE + '~~~(?:\\s*,\\s*' + JAVA_IDENT_RE + '~~~)*>)?', /~~~/g, 2); - const MAIN_KEYWORDS = [ - 'synchronized', - 'abstract', - 'private', - 'var', - 'static', - 'if', - 'const ', - 'for', - 'while', - 'strictfp', - 'finally', - 'protected', - 'import', - 'native', - 'final', - 'void', - 'enum', - 'else', - 'break', - 'transient', - 'catch', - 'instanceof', - 'volatile', - 'case', - 'assert', - 'package', - 'default', - 'public', - 'try', - 'switch', - 'continue', - 'throws', - 'protected', - 'public', - 'private', - 'module', - 'requires', - 'exports', - 'do', - 'sealed' - ]; - - const BUILT_INS = [ - 'super', - 'this' - ]; - - const LITERALS = [ - 'false', - 'true', - 'null' - ]; - - const TYPES = [ - 'char', - 'boolean', - 'long', - 'float', - 'int', - 'byte', - 'short', - 'double' - ]; - - const KEYWORDS = { - keyword: MAIN_KEYWORDS, - literal: LITERALS, - type: TYPES, - built_in: BUILT_INS - }; - - const ANNOTATION = { - className: 'meta', - begin: '@' + JAVA_IDENT_RE, - contains: [ - { - begin: /\(/, - end: /\)/, - contains: [ "self" ] // allow nested () inside our annotation - } - ] - }; - const PARAMS = { - className: 'params', - begin: /\(/, - end: /\)/, - keywords: KEYWORDS, - relevance: 0, - contains: [ hljs.C_BLOCK_COMMENT_MODE ], - endsParent: true - }; - - return { - name: 'Java', - aliases: [ 'jsp' ], - keywords: KEYWORDS, - illegal: /<\/|#/, - contains: [ - hljs.COMMENT( - '/\\*\\*', - '\\*/', - { - relevance: 0, - contains: [ - { - // eat up @'s in emails to prevent them to be recognized as doctags - begin: /\w+@/, - relevance: 0 - }, - { - className: 'doctag', - begin: '@[A-Za-z]+' - } - ] - } - ), - // relevance boost - { - begin: /import java\.[a-z]+\./, - keywords: "import", - relevance: 2 - }, - hljs.C_LINE_COMMENT_MODE, - hljs.C_BLOCK_COMMENT_MODE, - { - begin: /"""/, - end: /"""/, - className: "string", - contains: [ hljs.BACKSLASH_ESCAPE ] - }, - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE, - { - match: [ - /\b(?:class|interface|enum|extends|implements|new)/, - /\s+/, - JAVA_IDENT_RE - ], - className: { - 1: "keyword", - 3: "title.class" - } - }, - { - // Exceptions for hyphenated keywords - match: /non-sealed/, - scope: "keyword" - }, - { - begin: [ - regex.concat(/(?!else)/, JAVA_IDENT_RE), - /\s+/, - JAVA_IDENT_RE, - /\s+/, - /=(?!=)/ - ], - className: { - 1: "type", - 3: "variable", - 5: "operator" - } - }, - { - begin: [ - /record/, - /\s+/, - JAVA_IDENT_RE - ], - className: { - 1: "keyword", - 3: "title.class" - }, - contains: [ - PARAMS, - hljs.C_LINE_COMMENT_MODE, - hljs.C_BLOCK_COMMENT_MODE - ] - }, - { - // Expression keywords prevent 'keyword Name(...)' from being - // recognized as a function definition - beginKeywords: 'new throw return else', - relevance: 0 - }, - { - begin: [ - '(?:' + GENERIC_IDENT_RE + '\\s+)', - hljs.UNDERSCORE_IDENT_RE, - /\s*(?=\()/ - ], - className: { 2: "title.function" }, - keywords: KEYWORDS, - contains: [ - { - className: 'params', - begin: /\(/, - end: /\)/, - keywords: KEYWORDS, - relevance: 0, - contains: [ - ANNOTATION, - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE, - NUMERIC, - hljs.C_BLOCK_COMMENT_MODE - ] - }, - hljs.C_LINE_COMMENT_MODE, - hljs.C_BLOCK_COMMENT_MODE - ] - }, - NUMERIC, - ANNOTATION - ] - }; -} diff --git a/packages/web/src/third_party/highlightjs/highlightjs/languages/javascript.js b/packages/web/src/third_party/highlightjs/highlightjs/languages/javascript.js deleted file mode 100644 index 63c6fcbbf4..0000000000 --- a/packages/web/src/third_party/highlightjs/highlightjs/languages/javascript.js +++ /dev/null @@ -1,733 +0,0 @@ -const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*'; -const KEYWORDS = [ - "as", // for exports - "in", - "of", - "if", - "for", - "while", - "finally", - "var", - "new", - "function", - "do", - "return", - "void", - "else", - "break", - "catch", - "instanceof", - "with", - "throw", - "case", - "default", - "try", - "switch", - "continue", - "typeof", - "delete", - "let", - "yield", - "const", - "class", - // JS handles these with a special rule - // "get", - // "set", - "debugger", - "async", - "await", - "static", - "import", - "from", - "export", - "extends" -]; -const LITERALS = [ - "true", - "false", - "null", - "undefined", - "NaN", - "Infinity" -]; - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects -const TYPES = [ - // Fundamental objects - "Object", - "Function", - "Boolean", - "Symbol", - // numbers and dates - "Math", - "Date", - "Number", - "BigInt", - // text - "String", - "RegExp", - // Indexed collections - "Array", - "Float32Array", - "Float64Array", - "Int8Array", - "Uint8Array", - "Uint8ClampedArray", - "Int16Array", - "Int32Array", - "Uint16Array", - "Uint32Array", - "BigInt64Array", - "BigUint64Array", - // Keyed collections - "Set", - "Map", - "WeakSet", - "WeakMap", - // Structured data - "ArrayBuffer", - "SharedArrayBuffer", - "Atomics", - "DataView", - "JSON", - // Control abstraction objects - "Promise", - "Generator", - "GeneratorFunction", - "AsyncFunction", - // Reflection - "Reflect", - "Proxy", - // Internationalization - "Intl", - // WebAssembly - "WebAssembly" -]; - -const ERROR_TYPES = [ - "Error", - "EvalError", - "InternalError", - "RangeError", - "ReferenceError", - "SyntaxError", - "TypeError", - "URIError" -]; - -const BUILT_IN_GLOBALS = [ - "setInterval", - "setTimeout", - "clearInterval", - "clearTimeout", - - "require", - "exports", - - "eval", - "isFinite", - "isNaN", - "parseFloat", - "parseInt", - "decodeURI", - "decodeURIComponent", - "encodeURI", - "encodeURIComponent", - "escape", - "unescape" -]; - -const BUILT_IN_VARIABLES = [ - "arguments", - "this", - "super", - "console", - "window", - "document", - "localStorage", - "module", - "global" // Node.js -]; - -const BUILT_INS = [].concat( - BUILT_IN_GLOBALS, - TYPES, - ERROR_TYPES -); - -/* -Language: JavaScript -Description: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions. -Category: common, scripting, web -Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript -*/ - -/** @type LanguageFn */ -export default function javascript(hljs) { - const regex = hljs.regex; - /** - * Takes a string like " { - const tag = "', - end: '' - }; - // to avoid some special cases inside isTrulyOpeningTag - const XML_SELF_CLOSING = /<[A-Za-z0-9\\._:-]+\s*\/>/; - const XML_TAG = { - begin: /<[A-Za-z0-9\\._:-]+/, - end: /\/[A-Za-z0-9\\._:-]+>|\/>/, - /** - * @param {RegExpMatchArray} match - * @param {CallbackResponse} response - */ - isTrulyOpeningTag: (match, response) => { - const afterMatchIndex = match[0].length + match.index; - const nextChar = match.input[afterMatchIndex]; - if ( - // HTML should not include another raw `<` inside a tag - // nested type? - // `>`, etc. - nextChar === "<" || - // the , gives away that this is not HTML - // `` - nextChar === ",") { - response.ignoreMatch(); - return; - } - - // `` - // Quite possibly a tag, lets look for a matching closing tag... - if (nextChar === ">") { - // if we cannot find a matching closing tag, then we - // will ignore it - if (!hasClosingTag(match, { after: afterMatchIndex })) { - response.ignoreMatch(); - } - } - - // `` (self-closing) - // handled by simpleSelfClosing rule - - // `` - // technically this could be HTML, but it smells like a type - let m; - const afterMatch = match.input.substring(afterMatchIndex); - // NOTE: This is ugh, but added specifically for https://github.com/highlightjs/highlight.js/issues/3276 - if ((m = afterMatch.match(/^\s+extends\s+/))) { - if (m.index === 0) { - response.ignoreMatch(); - // eslint-disable-next-line no-useless-return - return; - } - } - } - }; - const KEYWORDS$1 = { - $pattern: IDENT_RE, - keyword: KEYWORDS, - literal: LITERALS, - built_in: BUILT_INS, - "variable.language": BUILT_IN_VARIABLES - }; - - // https://tc39.es/ecma262/#sec-literals-numeric-literals - const decimalDigits = '[0-9](_?[0-9])*'; - const frac = `\\.(${decimalDigits})`; - // DecimalIntegerLiteral, including Annex B NonOctalDecimalIntegerLiteral - // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals - const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`; - const NUMBER = { - className: 'number', - variants: [ - // DecimalLiteral - { begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))` + - `[eE][+-]?(${decimalDigits})\\b` }, - { begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` }, - - // DecimalBigIntegerLiteral - { begin: `\\b(0|[1-9](_?[0-9])*)n\\b` }, - - // NonDecimalIntegerLiteral - { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" }, - { begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" }, - { begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" }, - - // LegacyOctalIntegerLiteral (does not include underscore separators) - // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals - { begin: "\\b0[0-7]+n?\\b" }, - ], - relevance: 0 - }; - - const SUBST = { - className: 'subst', - begin: '\\$\\{', - end: '\\}', - keywords: KEYWORDS$1, - contains: [] // defined later - }; - const HTML_TEMPLATE = { - begin: 'html`', - end: '', - starts: { - end: '`', - returnEnd: false, - contains: [ - hljs.BACKSLASH_ESCAPE, - SUBST - ], - subLanguage: 'xml' - } - }; - const CSS_TEMPLATE = { - begin: 'css`', - end: '', - starts: { - end: '`', - returnEnd: false, - contains: [ - hljs.BACKSLASH_ESCAPE, - SUBST - ], - subLanguage: 'css' - } - }; - const TEMPLATE_STRING = { - className: 'string', - begin: '`', - end: '`', - contains: [ - hljs.BACKSLASH_ESCAPE, - SUBST - ] - }; - const JSDOC_COMMENT = hljs.COMMENT( - /\/\*\*(?!\/)/, - '\\*/', - { - relevance: 0, - contains: [ - { - begin: '(?=@[A-Za-z]+)', - relevance: 0, - contains: [ - { - className: 'doctag', - begin: '@[A-Za-z]+' - }, - { - className: 'type', - begin: '\\{', - end: '\\}', - excludeEnd: true, - excludeBegin: true, - relevance: 0 - }, - { - className: 'variable', - begin: IDENT_RE$1 + '(?=\\s*(-)|$)', - endsParent: true, - relevance: 0 - }, - // eat spaces (not newlines) so we can find - // types or variables - { - begin: /(?=[^\n])\s/, - relevance: 0 - } - ] - } - ] - } - ); - const COMMENT = { - className: "comment", - variants: [ - JSDOC_COMMENT, - hljs.C_BLOCK_COMMENT_MODE, - hljs.C_LINE_COMMENT_MODE - ] - }; - const SUBST_INTERNALS = [ - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE, - HTML_TEMPLATE, - CSS_TEMPLATE, - TEMPLATE_STRING, - NUMBER, - // This is intentional: - // See https://github.com/highlightjs/highlight.js/issues/3288 - // hljs.REGEXP_MODE - ]; - SUBST.contains = SUBST_INTERNALS - .concat({ - // we need to pair up {} inside our subst to prevent - // it from ending too early by matching another } - begin: /\{/, - end: /\}/, - keywords: KEYWORDS$1, - contains: [ - "self" - ].concat(SUBST_INTERNALS) - }); - const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains); - const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([ - // eat recursive parens in sub expressions - { - begin: /\(/, - end: /\)/, - keywords: KEYWORDS$1, - contains: ["self"].concat(SUBST_AND_COMMENTS) - } - ]); - const PARAMS = { - className: 'params', - begin: /\(/, - end: /\)/, - excludeBegin: true, - excludeEnd: true, - keywords: KEYWORDS$1, - contains: PARAMS_CONTAINS - }; - - // ES6 classes - const CLASS_OR_EXTENDS = { - variants: [ - // class Car extends vehicle - { - match: [ - /class/, - /\s+/, - IDENT_RE$1, - /\s+/, - /extends/, - /\s+/, - regex.concat(IDENT_RE$1, "(", regex.concat(/\./, IDENT_RE$1), ")*") - ], - scope: { - 1: "keyword", - 3: "title.class", - 5: "keyword", - 7: "title.class.inherited" - } - }, - // class Car - { - match: [ - /class/, - /\s+/, - IDENT_RE$1 - ], - scope: { - 1: "keyword", - 3: "title.class" - } - }, - - ] - }; - - const CLASS_REFERENCE = { - relevance: 0, - match: - regex.either( - // Hard coded exceptions - /\bJSON/, - // Float32Array, OutT - /\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/, - // CSSFactory, CSSFactoryT - /\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/, - // FPs, FPsT - /\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/, - // P - // single letters are not highlighted - // BLAH - // this will be flagged as a UPPER_CASE_CONSTANT instead - ), - className: "title.class", - keywords: { - _: [ - // se we still get relevance credit for JS library classes - ...TYPES, - ...ERROR_TYPES - ] - } - }; - - const USE_STRICT = { - label: "use_strict", - className: 'meta', - relevance: 10, - begin: /^\s*['"]use (strict|asm)['"]/ - }; - - const FUNCTION_DEFINITION = { - variants: [ - { - match: [ - /function/, - /\s+/, - IDENT_RE$1, - /(?=\s*\()/ - ] - }, - // anonymous function - { - match: [ - /function/, - /\s*(?=\()/ - ] - } - ], - className: { - 1: "keyword", - 3: "title.function" - }, - label: "func.def", - contains: [ PARAMS ], - illegal: /%/ - }; - - const UPPER_CASE_CONSTANT = { - relevance: 0, - match: /\b[A-Z][A-Z_0-9]+\b/, - className: "variable.constant" - }; - - function noneOf(list) { - return regex.concat("(?!", list.join("|"), ")"); - } - - const FUNCTION_CALL = { - match: regex.concat( - /\b/, - noneOf([ - ...BUILT_IN_GLOBALS, - "super" - ]), - IDENT_RE$1, regex.lookahead(/\(/)), - className: "title.function", - relevance: 0 - }; - - const PROPERTY_ACCESS = { - begin: regex.concat(/\./, regex.lookahead( - regex.concat(IDENT_RE$1, /(?![0-9A-Za-z$_(])/) - )), - end: IDENT_RE$1, - excludeBegin: true, - keywords: "prototype", - className: "property", - relevance: 0 - }; - - const GETTER_OR_SETTER = { - match: [ - /get|set/, - /\s+/, - IDENT_RE$1, - /(?=\()/ - ], - className: { - 1: "keyword", - 3: "title.function" - }, - contains: [ - { // eat to avoid empty params - begin: /\(\)/ - }, - PARAMS - ] - }; - - const FUNC_LEAD_IN_RE = '(\\(' + - '[^()]*(\\(' + - '[^()]*(\\(' + - '[^()]*' + - '\\)[^()]*)*' + - '\\)[^()]*)*' + - '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>'; - - const FUNCTION_VARIABLE = { - match: [ - /const|var|let/, /\s+/, - IDENT_RE$1, /\s*/, - /=\s*/, - /(async\s*)?/, // async is optional - regex.lookahead(FUNC_LEAD_IN_RE) - ], - keywords: "async", - className: { - 1: "keyword", - 3: "title.function" - }, - contains: [ - PARAMS - ] - }; - - return { - name: 'Javascript', - aliases: ['js', 'jsx', 'mjs', 'cjs'], - keywords: KEYWORDS$1, - // this will be extended by TypeScript - exports: { PARAMS_CONTAINS, CLASS_REFERENCE }, - illegal: /#(?![$_A-z])/, - contains: [ - hljs.SHEBANG({ - label: "shebang", - binary: "node", - relevance: 5 - }), - USE_STRICT, - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE, - HTML_TEMPLATE, - CSS_TEMPLATE, - TEMPLATE_STRING, - COMMENT, - NUMBER, - CLASS_REFERENCE, - { - className: 'attr', - begin: IDENT_RE$1 + regex.lookahead(':'), - relevance: 0 - }, - FUNCTION_VARIABLE, - { // "value" container - begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*', - keywords: 'return throw case', - relevance: 0, - contains: [ - COMMENT, - hljs.REGEXP_MODE, - { - className: 'function', - // we have to count the parens to make sure we actually have the - // correct bounding ( ) before the =>. There could be any number of - // sub-expressions inside also surrounded by parens. - begin: FUNC_LEAD_IN_RE, - returnBegin: true, - end: '\\s*=>', - contains: [ - { - className: 'params', - variants: [ - { - begin: hljs.UNDERSCORE_IDENT_RE, - relevance: 0 - }, - { - className: null, - begin: /\(\s*\)/, - skip: true - }, - { - begin: /\(/, - end: /\)/, - excludeBegin: true, - excludeEnd: true, - keywords: KEYWORDS$1, - contains: PARAMS_CONTAINS - } - ] - } - ] - }, - { // could be a comma delimited list of params to a function call - begin: /,/, - relevance: 0 - }, - { - match: /\s+/, - relevance: 0 - }, - { // JSX - variants: [ - { begin: FRAGMENT.begin, end: FRAGMENT.end }, - { match: XML_SELF_CLOSING }, - { - begin: XML_TAG.begin, - // we carefully check the opening tag to see if it truly - // is a tag and not a false positive - 'on:begin': XML_TAG.isTrulyOpeningTag, - end: XML_TAG.end - } - ], - subLanguage: 'xml', - contains: [ - { - begin: XML_TAG.begin, - end: XML_TAG.end, - skip: true, - contains: ['self'] - } - ] - } - ], - }, - FUNCTION_DEFINITION, - { - // prevent this from getting swallowed up by function - // since they appear "function like" - beginKeywords: "while if switch catch for" - }, - { - // we have to count the parens to make sure we actually have the correct - // bounding ( ). There could be any number of sub-expressions inside - // also surrounded by parens. - begin: '\\b(?!function)' + hljs.UNDERSCORE_IDENT_RE + - '\\(' + // first parens - '[^()]*(\\(' + - '[^()]*(\\(' + - '[^()]*' + - '\\)[^()]*)*' + - '\\)[^()]*)*' + - '\\)\\s*\\{', // end parens - returnBegin:true, - label: "func.def", - contains: [ - PARAMS, - hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1, className: "title.function" }) - ] - }, - // catch ... so it won't trigger the property rule below - { - match: /\.\.\./, - relevance: 0 - }, - PROPERTY_ACCESS, - // hack: prevents detection of keywords in some circumstances - // .keyword() - // $keyword = x - { - match: '\\$' + IDENT_RE$1, - relevance: 0 - }, - { - match: [ /\bconstructor(?=\s*\()/ ], - className: { 1: "title.function" }, - contains: [ PARAMS ] - }, - FUNCTION_CALL, - UPPER_CASE_CONSTANT, - CLASS_OR_EXTENDS, - GETTER_OR_SETTER, - { - match: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something` - } - ] - }; -} diff --git a/packages/web/src/third_party/highlightjs/highlightjs/languages/python.js b/packages/web/src/third_party/highlightjs/highlightjs/languages/python.js deleted file mode 100644 index 88df42354e..0000000000 --- a/packages/web/src/third_party/highlightjs/highlightjs/languages/python.js +++ /dev/null @@ -1,432 +0,0 @@ -/* -Language: Python -Description: Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. -Website: https://www.python.org -Category: common -*/ - -export default function python(hljs) { - const regex = hljs.regex; - const IDENT_RE = /[\p{XID_Start}_]\p{XID_Continue}*/u; - const RESERVED_WORDS = [ - 'and', - 'as', - 'assert', - 'async', - 'await', - 'break', - 'case', - 'class', - 'continue', - 'def', - 'del', - 'elif', - 'else', - 'except', - 'finally', - 'for', - 'from', - 'global', - 'if', - 'import', - 'in', - 'is', - 'lambda', - 'match', - 'nonlocal|10', - 'not', - 'or', - 'pass', - 'raise', - 'return', - 'try', - 'while', - 'with', - 'yield' - ]; - - const BUILT_INS = [ - '__import__', - 'abs', - 'all', - 'any', - 'ascii', - 'bin', - 'bool', - 'breakpoint', - 'bytearray', - 'bytes', - 'callable', - 'chr', - 'classmethod', - 'compile', - 'complex', - 'delattr', - 'dict', - 'dir', - 'divmod', - 'enumerate', - 'eval', - 'exec', - 'filter', - 'float', - 'format', - 'frozenset', - 'getattr', - 'globals', - 'hasattr', - 'hash', - 'help', - 'hex', - 'id', - 'input', - 'int', - 'isinstance', - 'issubclass', - 'iter', - 'len', - 'list', - 'locals', - 'map', - 'max', - 'memoryview', - 'min', - 'next', - 'object', - 'oct', - 'open', - 'ord', - 'pow', - 'print', - 'property', - 'range', - 'repr', - 'reversed', - 'round', - 'set', - 'setattr', - 'slice', - 'sorted', - 'staticmethod', - 'str', - 'sum', - 'super', - 'tuple', - 'type', - 'vars', - 'zip' - ]; - - const LITERALS = [ - '__debug__', - 'Ellipsis', - 'False', - 'None', - 'NotImplemented', - 'True' - ]; - - // https://docs.python.org/3/library/typing.html - // TODO: Could these be supplemented by a CamelCase matcher in certain - // contexts, leaving these remaining only for relevance hinting? - const TYPES = [ - "Any", - "Callable", - "Coroutine", - "Dict", - "List", - "Literal", - "Generic", - "Optional", - "Sequence", - "Set", - "Tuple", - "Type", - "Union" - ]; - - const KEYWORDS = { - $pattern: /[A-Za-z]\w+|__\w+__/, - keyword: RESERVED_WORDS, - built_in: BUILT_INS, - literal: LITERALS, - type: TYPES - }; - - const PROMPT = { - className: 'meta', - begin: /^(>>>|\.\.\.) / - }; - - const SUBST = { - className: 'subst', - begin: /\{/, - end: /\}/, - keywords: KEYWORDS, - illegal: /#/ - }; - - const LITERAL_BRACKET = { - begin: /\{\{/, - relevance: 0 - }; - - const STRING = { - className: 'string', - contains: [ hljs.BACKSLASH_ESCAPE ], - variants: [ - { - begin: /([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/, - end: /'''/, - contains: [ - hljs.BACKSLASH_ESCAPE, - PROMPT - ], - relevance: 10 - }, - { - begin: /([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/, - end: /"""/, - contains: [ - hljs.BACKSLASH_ESCAPE, - PROMPT - ], - relevance: 10 - }, - { - begin: /([fF][rR]|[rR][fF]|[fF])'''/, - end: /'''/, - contains: [ - hljs.BACKSLASH_ESCAPE, - PROMPT, - LITERAL_BRACKET, - SUBST - ] - }, - { - begin: /([fF][rR]|[rR][fF]|[fF])"""/, - end: /"""/, - contains: [ - hljs.BACKSLASH_ESCAPE, - PROMPT, - LITERAL_BRACKET, - SUBST - ] - }, - { - begin: /([uU]|[rR])'/, - end: /'/, - relevance: 10 - }, - { - begin: /([uU]|[rR])"/, - end: /"/, - relevance: 10 - }, - { - begin: /([bB]|[bB][rR]|[rR][bB])'/, - end: /'/ - }, - { - begin: /([bB]|[bB][rR]|[rR][bB])"/, - end: /"/ - }, - { - begin: /([fF][rR]|[rR][fF]|[fF])'/, - end: /'/, - contains: [ - hljs.BACKSLASH_ESCAPE, - LITERAL_BRACKET, - SUBST - ] - }, - { - begin: /([fF][rR]|[rR][fF]|[fF])"/, - end: /"/, - contains: [ - hljs.BACKSLASH_ESCAPE, - LITERAL_BRACKET, - SUBST - ] - }, - hljs.APOS_STRING_MODE, - hljs.QUOTE_STRING_MODE - ] - }; - - // https://docs.python.org/3.9/reference/lexical_analysis.html#numeric-literals - const digitpart = '[0-9](_?[0-9])*'; - const pointfloat = `(\\b(${digitpart}))?\\.(${digitpart})|\\b(${digitpart})\\.`; - // Whitespace after a number (or any lexical token) is needed only if its absence - // would change the tokenization - // https://docs.python.org/3.9/reference/lexical_analysis.html#whitespace-between-tokens - // We deviate slightly, requiring a word boundary or a keyword - // to avoid accidentally recognizing *prefixes* (e.g., `0` in `0x41` or `08` or `0__1`) - const lookahead = `\\b|${RESERVED_WORDS.join('|')}`; - const NUMBER = { - className: 'number', - relevance: 0, - variants: [ - // exponentfloat, pointfloat - // https://docs.python.org/3.9/reference/lexical_analysis.html#floating-point-literals - // optionally imaginary - // https://docs.python.org/3.9/reference/lexical_analysis.html#imaginary-literals - // Note: no leading \b because floats can start with a decimal point - // and we don't want to mishandle e.g. `fn(.5)`, - // no trailing \b for pointfloat because it can end with a decimal point - // and we don't want to mishandle e.g. `0..hex()`; this should be safe - // because both MUST contain a decimal point and so cannot be confused with - // the interior part of an identifier - { - begin: `(\\b(${digitpart})|(${pointfloat}))[eE][+-]?(${digitpart})[jJ]?(?=${lookahead})` - }, - { - begin: `(${pointfloat})[jJ]?` - }, - - // decinteger, bininteger, octinteger, hexinteger - // https://docs.python.org/3.9/reference/lexical_analysis.html#integer-literals - // optionally "long" in Python 2 - // https://docs.python.org/2.7/reference/lexical_analysis.html#integer-and-long-integer-literals - // decinteger is optionally imaginary - // https://docs.python.org/3.9/reference/lexical_analysis.html#imaginary-literals - { - begin: `\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${lookahead})` - }, - { - begin: `\\b0[bB](_?[01])+[lL]?(?=${lookahead})` - }, - { - begin: `\\b0[oO](_?[0-7])+[lL]?(?=${lookahead})` - }, - { - begin: `\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${lookahead})` - }, - - // imagnumber (digitpart-based) - // https://docs.python.org/3.9/reference/lexical_analysis.html#imaginary-literals - { - begin: `\\b(${digitpart})[jJ](?=${lookahead})` - } - ] - }; - const COMMENT_TYPE = { - className: "comment", - begin: regex.lookahead(/# type:/), - end: /$/, - keywords: KEYWORDS, - contains: [ - { // prevent keywords from coloring `type` - begin: /# type:/ - }, - // comment within a datatype comment includes no keywords - { - begin: /#/, - end: /\b\B/, - endsWithParent: true - } - ] - }; - const PARAMS = { - className: 'params', - variants: [ - // Exclude params in functions without params - { - className: "", - begin: /\(\s*\)/, - skip: true - }, - { - begin: /\(/, - end: /\)/, - excludeBegin: true, - excludeEnd: true, - keywords: KEYWORDS, - contains: [ - 'self', - PROMPT, - NUMBER, - STRING, - hljs.HASH_COMMENT_MODE - ] - } - ] - }; - SUBST.contains = [ - STRING, - NUMBER, - PROMPT - ]; - - return { - name: 'Python', - aliases: [ - 'py', - 'gyp', - 'ipython' - ], - unicodeRegex: true, - keywords: KEYWORDS, - illegal: /(<\/|->|\?)|=>/, - contains: [ - PROMPT, - NUMBER, - { - // very common convention - begin: /\bself\b/ - }, - { - // eat "if" prior to string so that it won't accidentally be - // labeled as an f-string - beginKeywords: "if", - relevance: 0 - }, - STRING, - COMMENT_TYPE, - hljs.HASH_COMMENT_MODE, - { - match: [ - /\bdef/, /\s+/, - IDENT_RE, - ], - scope: { - 1: "keyword", - 3: "title.function" - }, - contains: [ PARAMS ] - }, - { - variants: [ - { - match: [ - /\bclass/, /\s+/, - IDENT_RE, /\s*/, - /\(\s*/, IDENT_RE,/\s*\)/ - ], - }, - { - match: [ - /\bclass/, /\s+/, - IDENT_RE - ], - } - ], - scope: { - 1: "keyword", - 3: "title.class", - 6: "title.class.inherited", - } - }, - { - className: 'meta', - begin: /^[\t ]*@/, - end: /(?=#)|$/, - contains: [ - NUMBER, - PARAMS, - STRING - ] - } - ] - }; -} diff --git a/packages/web/src/third_party/highlightjs/roll.sh b/packages/web/src/third_party/highlightjs/roll.sh deleted file mode 100755 index 59aac6361a..0000000000 --- a/packages/web/src/third_party/highlightjs/roll.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -set -e -set +x - -# Pick a stable release revision from here: -# https://github.com/highlightjs/highlight.js/releases -RELEASE_REVISION="bed790f3f3515ebcb92896ab23a518f835008233" -LANGUAGES="javascript python csharp java" -STYLES="github*.css" - -trap "cd $(pwd -P)" EXIT -SCRIPT_PATH="$(cd "$(dirname "$0")" ; pwd -P)" - -cd "$(dirname "$0")" -rm -rf ./output -mkdir -p ./output - -cd ./output -git clone git@github.com:highlightjs/highlight.js.git -cd ./highlight.js -git checkout ${RELEASE_REVISION} -npm install -node tools/build.js -t node ${LANGUAGES} - -cd ../.. -rm -rf ./highlightjs -mkdir -p ./highlightjs -cp -R output/highlight.js/build/lib/* highlightjs/ -cp output/highlight.js/build/LICENSE highlightjs/ -cp output/highlight.js/build/types/index.d.ts highlightjs/ -cp output/highlight.js/build/styles/${STYLES} highlightjs/ -echo $'\n'"export = hljs;"$'\n' >> highlightjs/index.d.ts -rm -rf ./output diff --git a/tests/library/inspector/pause.spec.ts b/tests/library/inspector/pause.spec.ts index 1b497d995a..cc046c3633 100644 --- a/tests/library/inspector/pause.spec.ts +++ b/tests/library/inspector/pause.spec.ts @@ -89,7 +89,7 @@ it.describe('pause', () => { await page.pause(); })(); const recorderPage = await recorderPageGetter(); - const source = await recorderPage.textContent('.source-line-paused .source-code'); + const source = await recorderPage.textContent('.source-line-paused'); expect(source).toContain('page.pause()'); await recorderPage.click('[title="Resume (F8)"]'); await scriptPromise; diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index bd26a87aef..2f734b076c 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -542,10 +542,6 @@ test('should show action source', async ({ showTraceViewer }) => { const page = traceViewer.page; await page.click('text=Source'); - await expect(page.locator('.source-line')).toContainText([ - /async.*function.*doClick/, - /page\.click/ - ]); await expect(page.locator('.source-line-running')).toContainText('await page.getByText(\'Click\').click()'); await expect(page.locator('.stack-trace-frame.selected')).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/); }); diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 306ffe87dd..48c5d79e1c 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -383,7 +383,7 @@ test('should show trace source', async ({ runInlineTest, page, showReport }) => await page.click('.action-title >> text=page.evaluate'); await page.click('text=Source'); - await expect(page.locator('.source-line')).toContainText([ + await expect(page.locator('.CodeMirror-line')).toContainText([ /const.*pwt;/, /page\.evaluate/ ]); diff --git a/utils/build/build.js b/utils/build/build.js index 970fed9c70..30a3c5b3fb 100644 --- a/utils/build/build.js +++ b/utils/build/build.js @@ -291,6 +291,21 @@ for (const webPackage of ['html-reporter', 'recorder', 'trace-viewer']) { }); } +// Rebuild web projects service workers on change. +for (const webPackage of ['trace-viewer']) { + onChanges.push({ + committed: false, + inputs: [ + `packages/${webPackage}/src/`, + `packages/${webPackage}/view.sw.config.ts`, + `packages/web/src/`, + ], + command: 'npx', + args: ['vite', '--config', 'vite.sw.config.ts', 'build', ...(watchMode ? ['--sourcemap'] : [])], + cwd: path.join(__dirname, '..', '..', 'packages', webPackage), + }); +} + // The recorder and trace viewer have an app_icon.png that needs to be copied. copyFiles.push({ files: 'packages/playwright-core/src/server/chromium/*.png', diff --git a/utils/generate_third_party_notice.js b/utils/generate_third_party_notice.js index 0274e6af24..0317d7f8b8 100644 --- a/utils/generate_third_party_notice.js +++ b/utils/generate_third_party_notice.js @@ -19,6 +19,23 @@ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); +async function checkDir(dir) { + return await new Promise((f, r) => { + checker.init({ + start: dir, + production: true, + customPath: { + licenseText: '', + } + }, function(err, packages) { + if (err) + r(err); + else + f(packages); + }); + }); +} + (async () => { for (const project of ['playwright-core', 'playwright-test']) { const lines = []; @@ -35,26 +52,19 @@ This project incorporates components from the projects listed below. The origina for (const bundle of fs.readdirSync(bundlesDir)) { const dir = path.join(bundlesDir, bundle); execSync('npm ci', { cwd: dir }); - const packages = await new Promise((f, r) => { - checker.init({ - start: dir, - production: true, - customPath: { - licenseText: '', - } - }, function(err, packages) { - if (err) - r(err); - else - f(packages); - }); - }); + const packages = await checkDir(dir); for (const [key, value] of Object.entries(packages)) { if (value.licenseText) allPackages[key] = value; } } + const packages = await checkDir('node_modules/codemirror'); + for (const [key, value] of Object.entries(packages)) { + if (value.licenseText) + allPackages[key] = value; + } + const keys = Object.keys(allPackages).sort(); for (const key of keys) lines.push(`-\t${key} (${allPackages[key].repository})`);