瀏覽代碼

Merge branch 'main' into feature/json_docsym

tamayika 1 年之前
父節點
當前提交
c38f07a36e
共有 67 個文件被更改,包括 2535 次插入1024 次删除
  1. 21 0
      .azure-pipelines/publish-nightly.yml
  2. 5 0
      .azure-pipelines/publish-stable.yml
  3. 1 1
      .github/workflows/ci.yml
  4. 0 103
      .github/workflows/publish/computeState.js
  5. 29 15
      .github/workflows/website.yml
  6. 6 0
      .vscode/launch.json
  7. 60 0
      CHANGELOG.md
  8. 23 19
      MAINTAINING.md
  9. 27 1
      README.md
  10. 10 0
      build/importTypescript.ts
  11. 0 102
      build/simpleserver.ts
  12. 559 141
      package-lock.json
  13. 4 4
      package.json
  14. 9 9
      samples/browser-esm-vite-react/package-lock.json
  15. 1 1
      samples/browser-esm-vite-react/package.json
  16. 15 0
      scripts/ci/env.ts
  17. 6 2
      scripts/ci/monaco-editor-core-prepare.ts
  18. 5 1
      scripts/ci/monaco-editor-prepare.ts
  19. 5 2
      scripts/lib/index.ts
  20. 33 0
      src/basic-languages/elixir/elixir.test.ts
  21. 10 9
      src/basic-languages/elixir/elixir.ts
  22. 59 0
      src/basic-languages/javascript/javascript.test.ts
  23. 1 0
      src/basic-languages/javascript/javascript.ts
  24. 24 0
      src/basic-languages/mdx/mdx.contribution.ts
  25. 171 0
      src/basic-languages/mdx/mdx.test.ts
  26. 163 0
      src/basic-languages/mdx/mdx.ts
  27. 1 0
      src/basic-languages/monaco.contribution.ts
  28. 1 1
      src/basic-languages/st/st.contribution.ts
  29. 2 1
      src/basic-languages/st/st.ts
  30. 33 0
      src/basic-languages/twig/twig.test.ts
  31. 1 1
      src/basic-languages/twig/twig.ts
  32. 59 0
      src/basic-languages/typescript/typescript.test.ts
  33. 1 1
      src/basic-languages/typescript/typescript.ts
  34. 1 3
      src/basic-languages/wgsl/wgsl.ts
  35. 1 1
      src/language/typescript/languageFeatures.ts
  36. 2 2
      src/language/typescript/lib/typescriptServices.js
  37. 6 4
      src/language/typescript/ts.worker.ts
  38. 8 8
      test/manual/dev-setup.js
  39. 5 11
      test/manual/index.js
  40. 2 2
      webpack-plugin/package-lock.json
  41. 1 1
      webpack-plugin/package.json
  42. 7 3
      webpack-plugin/src/loaders/include.ts
  43. 92 0
      website/index/samples/sample.mdx.txt
  44. 1 1
      website/package.json
  45. 1 1
      website/src/monaco-loader.ts
  46. 46 5
      website/src/runner/index.ts
  47. 12 2
      website/src/shared.ts
  48. 40 0
      website/src/website/components/Loader.tsx
  49. 1 0
      website/src/website/components/monaco/MonacoEditor.tsx
  50. 1 0
      website/src/website/components/monaco/MonacoLoader.tsx
  51. 91 0
      website/src/website/data/home-samples/sample.mdx.txt
  52. 14 4
      website/src/website/data/playground-samples/creating-the-diffeditor/hello-diff-world/sample.js
  53. 3 3
      website/src/website/data/playground-samples/creating-the-editor/hello-world/sample.js
  54. 2 1
      website/src/website/data/playground-samples/interacting-with-the-editor/rendering-glyphs-in-the-margin/sample.css
  55. 7 8
      website/src/website/pages/home/Home.tsx
  56. 161 0
      website/src/website/pages/playground/BisectModel.ts
  57. 211 0
      website/src/website/pages/playground/LocationModel.ts
  58. 92 462
      website/src/website/pages/playground/PlaygroundModel.ts
  59. 12 0
      website/src/website/pages/playground/PlaygroundPage.tsx
  60. 145 56
      website/src/website/pages/playground/PlaygroundPageContent.tsx
  61. 67 20
      website/src/website/pages/playground/Preview.tsx
  62. 107 0
      website/src/website/pages/playground/Source.ts
  63. 8 1
      website/src/website/pages/playground/getNpmVersionsSync.ts
  64. 29 0
      website/src/website/pages/playground/utils.ts
  65. 3 0
      website/src/website/style.scss
  66. 1 1
      website/static/monarch/monarch.js
  67. 10 10
      website/yarn.lock

+ 21 - 0
.azure-pipelines/publish-nightly.yml

@@ -23,6 +23,16 @@ resources:
       ref: main
       ref: main
       endpoint: Monaco
       endpoint: Monaco
 
 
+parameters:
+  - name: vscodeRef
+    displayName: The VS Code commit id.
+    type: string
+    default: 'main'
+  - name: prereleaseVersion
+    displayName: The prerelease version.
+    type: string
+    default: 'dev-${today}'
+
 extends:
 extends:
   template: azure-pipelines/npm-package/pipeline.yml@templates
   template: azure-pipelines/npm-package/pipeline.yml@templates
   parameters:
   parameters:
@@ -31,10 +41,17 @@ extends:
         workingDirectory: $(Build.SourcesDirectory)/dependencies/vscode/out-monaco-editor-core
         workingDirectory: $(Build.SourcesDirectory)/dependencies/vscode/out-monaco-editor-core
         testPlatforms: []
         testPlatforms: []
         buildSteps:
         buildSteps:
+          - script: sudo apt install -y libkrb5-dev
+            displayName: Install libkrb5-dev
+
           - script: npm ci
           - script: npm ci
             displayName: Install NPM dependencies
             displayName: Install NPM dependencies
 
 
           - script: yarn ts-node ./scripts/ci/monaco-editor-core-prepare nightly
           - script: yarn ts-node ./scripts/ci/monaco-editor-core-prepare nightly
+            env:
+              VSCODE_REF: ${{ parameters.vscodeRef }}
+              PRERELEASE_VERSION: ${{ parameters.prereleaseVersion }}
+            retryCountOnTaskFailure: 5
             displayName: Setup, Build & Test monaco-editor-core
             displayName: Setup, Build & Test monaco-editor-core
 
 
         tag: next
         tag: next
@@ -50,6 +67,10 @@ extends:
             displayName: Install NPM dependencies
             displayName: Install NPM dependencies
 
 
           - script: yarn ts-node ./scripts/ci/monaco-editor-prepare nightly
           - script: yarn ts-node ./scripts/ci/monaco-editor-prepare nightly
+            env:
+              VSCODE_REF: ${{ parameters.vscodeRef }}
+              PRERELEASE_VERSION: ${{ parameters.prereleaseVersion }}
+            retryCountOnTaskFailure: 5
             displayName: Setup, Build & Test monaco-editor
             displayName: Setup, Build & Test monaco-editor
 
 
         tag: next
         tag: next

+ 5 - 0
.azure-pipelines/publish-stable.yml

@@ -37,6 +37,9 @@ extends:
         workingDirectory: $(Build.SourcesDirectory)/dependencies/vscode/out-monaco-editor-core
         workingDirectory: $(Build.SourcesDirectory)/dependencies/vscode/out-monaco-editor-core
         testPlatforms: []
         testPlatforms: []
         buildSteps:
         buildSteps:
+          - script: sudo apt install -y libkrb5-dev
+            displayName: Install libkrb5-dev
+
           - script: npm ci
           - script: npm ci
             displayName: Install NPM dependencies
             displayName: Install NPM dependencies
 
 
@@ -68,9 +71,11 @@ extends:
         buildSteps:
         buildSteps:
           - script: npm ci
           - script: npm ci
             displayName: Install NPM dependencies
             displayName: Install NPM dependencies
+            workingDirectory: $(Build.SourcesDirectory)/webpack-plugin
 
 
           - script: npm run compile
           - script: npm run compile
             displayName: Build plugin
             displayName: Build plugin
+            workingDirectory: $(Build.SourcesDirectory)/webpack-plugin
 
 
         tag: latest
         tag: latest
         ghCreateTag: false
         ghCreateTag: false

+ 1 - 1
.github/workflows/ci.yml

@@ -18,7 +18,7 @@ jobs:
         uses: actions/cache@v2
         uses: actions/cache@v2
         with:
         with:
           path: '**/node_modules'
           path: '**/node_modules'
-          key: ${{ runner.os }}-cacheNodeModules2-${{ hashFiles('**/package-lock.json') }}
+          key: ${{ runner.os }}-cacheNodeModules2-${{ hashFiles('**/package-lock.json', '**/package.json') }}
           restore-keys: ${{ runner.os }}-cacheNodeModules2-
           restore-keys: ${{ runner.os }}-cacheNodeModules2-
 
 
       - name: execute `npm ci` (1)
       - name: execute `npm ci` (1)

+ 0 - 103
.github/workflows/publish/computeState.js

@@ -1,103 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-//@ts-check
-
-const fs = require('fs');
-const cp = require('child_process');
-const packageJson = require('../../../package.json');
-
-if (process.argv.length !== 4) {
-	console.error(`usage: node computeState.js <"workflow_dispatch"|"schedule"> <"true"|"false">`);
-	process.exit(1);
-}
-
-const EVENT_NAME = /** @type {'workflow_dispatch'|'schedule'} */ (process.argv[2]);
-const STR_NIGHTLY = /** @type {'true'|'false'|''} */ (process.argv[3]);
-
-if (!/^((workflow_dispatch)|(schedule))$/.test(EVENT_NAME)) {
-	console.error(`usage: node computeState.js <"workflow_dispatch"|"schedule"> <"true"|"false">`);
-	process.exit(2);
-}
-
-if (!/^((true)|(false)|())$/.test(STR_NIGHTLY)) {
-	console.error(`usage: node computeState.js <"workflow_dispatch"|"schedule"> <"true"|"false">`);
-	process.exit(3);
-}
-
-const NIGHTLY = EVENT_NAME === 'schedule' || STR_NIGHTLY === 'true';
-
-const distTag = NIGHTLY ? 'next' : 'latest';
-
-const latestMonacoEditorVersion = npmGetLatestVersion('monaco-editor');
-const version = (() => {
-	if (NIGHTLY) {
-		const pieces = latestMonacoEditorVersion.split('.');
-		const minor = parseInt(pieces[1], 10);
-		const date = new Date();
-		const yyyy = date.getUTCFullYear();
-		const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
-		const dd = String(date.getUTCDate()).padStart(2, '0');
-		return `0.${minor + 1}.0-dev.${yyyy}${mm}${dd}`;
-	} else {
-		return packageJson.version;
-	}
-})();
-
-const vscodeBranch = (() => {
-	if (NIGHTLY) {
-		return 'main';
-	} else {
-		return packageJson.vscode;
-	}
-})();
-
-const skipMonacoEditorCore = (() => {
-	return /** @type {'true'|'false'} */ (String(npmExists('monaco-editor-core', version)));
-})();
-
-const skipMonacoEditor = (() => {
-	return /** @type {'true'|'false'} */ (String(npmExists('monaco-editor', version)));
-})();
-
-console.log(`
-::set-output name=dist_tag::${distTag}
-::set-output name=version::${version}
-::set-output name=vscode_branch::${vscodeBranch}
-::set-output name=skip_monaco_editor_core::${skipMonacoEditorCore}
-::set-output name=skip_monaco_editor::${skipMonacoEditor}
-`);
-
-/**
- * @param {string} packageName
- * @returns {string}
- */
-function npmGetLatestVersion(packageName) {
-	const output = cp.execSync(`npm show ${packageName} version`).toString();
-	const version = output.split(/\r\n|\r|\n/g)[0];
-	if (!/^0\.(\d+)\.(\d+)$/.test(version)) {
-		console.error(`version ${version} does not match 0.x.y`);
-		process.exit(1);
-	}
-	return version;
-}
-
-/**
- * @param {string} packageName
- * @param {string} version
- * @returns {boolean}
- */
-function npmExists(packageName, version) {
-	try {
-		const output = cp.execSync(`npm show ${packageName}@${version} version`).toString();
-		const result = output.split(/\r\n|\r|\n/g)[0];
-		if (result.trim().length === 0) {
-			return false;
-		}
-		return true;
-	} catch (err) {
-		return false;
-	}
-}

+ 29 - 15
.github/workflows/website.yml

@@ -1,23 +1,34 @@
 name: Publish Website
 name: Publish Website
 
 
 on:
 on:
-  push:
-    tags:
-      - 'v*'
-  # enable users to manually trigger with workflow_dispatch
+  schedule:
+    - cron: 0 23 * * *
   workflow_dispatch: {}
   workflow_dispatch: {}
 
 
+# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+permissions:
+  contents: read
+  pages: write
+  id-token: write
+
+# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
+# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
+concurrency:
+  group: 'pages'
+  cancel-in-progress: false
+
 jobs:
 jobs:
-  publish-website:
-    name: Publish Website
+  deploy:
+    environment:
+      name: github-pages
+      url: ${{ steps.deployment.outputs.page_url }}
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
-      - uses: actions/checkout@dc323e67f16fb5f7663d20ff7941f27f5809e9b6 # pin@v2
-
+      - name: Checkout
+        uses: actions/checkout@v3
       - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # pin@v2
       - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # pin@v2
         with:
         with:
           node-version: 16
           node-version: 16
-
       - name: Cache node modules
       - name: Cache node modules
         id: cacheNodeModules
         id: cacheNodeModules
         uses: actions/cache@v2
         uses: actions/cache@v2
@@ -25,11 +36,9 @@ jobs:
           path: '**/node_modules'
           path: '**/node_modules'
           key: ${{ runner.os }}-cacheNodeModules2-${{ hashFiles('**/package-lock.json') }}
           key: ${{ runner.os }}-cacheNodeModules2-${{ hashFiles('**/package-lock.json') }}
           restore-keys: ${{ runner.os }}-cacheNodeModules2-
           restore-keys: ${{ runner.os }}-cacheNodeModules2-
-
       - name: execute `npm ci` (1)
       - name: execute `npm ci` (1)
         if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
         if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
         run: npm ci
         run: npm ci
-
       - name: Build
       - name: Build
         run: npm run build-monaco-editor
         run: npm run build-monaco-editor
 
 
@@ -45,8 +54,13 @@ jobs:
         working-directory: website
         working-directory: website
         run: yarn run build
         run: yarn run build
 
 
-      - name: Upload website to github pages
-        uses: peaceiris/actions-gh-pages@bd8c6b06eba6b3d25d72b7a1767993c0aeee42e7 # pin@v3
+      - name: Setup Pages
+        uses: actions/configure-pages@v3
+      - name: Upload artifact
+        uses: actions/upload-pages-artifact@v1
         with:
         with:
-          github_token: ${{ secrets.GITHUB_TOKEN }}
-          publish_dir: ./website/dist
+          # Upload entire repository
+          path: './website/dist'
+      - name: Deploy to GitHub Pages
+        id: deployment
+        uses: actions/deploy-pages@v2

+ 6 - 0
.vscode/launch.json

@@ -15,6 +15,12 @@
 				"order": 1
 				"order": 1
 			}
 			}
 		},
 		},
+		{
+			"name": "Website",
+			"type": "chrome",
+			"request": "launch",
+			"url": "http://localhost:8080/"
+		},
 		{
 		{
 			// Clone VS Code and make sure the task "Launch Http Server" runs.
 			// Clone VS Code and make sure the task "Launch Http Server" runs.
 			// Then the editor is build from sources.
 			// Then the editor is build from sources.

+ 60 - 0
CHANGELOG.md

@@ -1,5 +1,65 @@
 # Monaco Editor Changelog
 # Monaco Editor Changelog
 
 
+## [0.42.0]
+
+- Uses new diff editor widget by default. Use `experimental.useVersion2: false` to use the old widget. The old widget will be replaced in the next update.
+- Diff editor uses inline mode by default when width is too small. Use the config option `useInlineViewWhenSpaceIsLimited` to control this behavior.
+- Fixes broken language features when a model is created before the editor.
+
+## [0.41.0]
+
+- `IDiffEditor.diffReviewNext` was renamed to `IDiffEditor.accessibleDiffViewerNext`.
+- `IDiffEditor.diffReviewPrev` was renamed to `IDiffEditor.accessibleDiffViewerPrev`.
+- Introduces `InlineCompletionsProvider.yieldsToGroupIds` to allows inline completion providers to yield to other providers.
+- Bugfixes
+
+Contributions to `monaco-editor`:
+
+- [@claylibrarymarket](https://github.com/claylibrarymarket): Fix Twig's plain text class expression [PR #4063](https://github.com/microsoft/monaco-editor/pull/4063)
+- [@FossPrime (Ray Foss)](https://github.com/FossPrime): Use new GitHub pages workflow [PR #4000](https://github.com/microsoft/monaco-editor/pull/4000)
+- [@leandrocp (Leandro Pereira)](https://github.com/leandrocp): Elixir - Add support for multi-letter uppercase sigils [PR #4041](https://github.com/microsoft/monaco-editor/pull/4041)
+- [@philippleidig (PhilippLe)](https://github.com/philippleidig): Add TwinCAT file support for structured text (st) language [PR #3315](https://github.com/microsoft/monaco-editor/pull/3315)
+- [@remcohaszing (Remco Haszing)](https://github.com/remcohaszing)
+  - Add mdx language [PR #3096](https://github.com/microsoft/monaco-editor/pull/3096)
+  - Export custom TypeScript worker variables [PR #3488](https://github.com/microsoft/monaco-editor/pull/3488)
+  - Document some basic concepts [PR #4087](https://github.com/microsoft/monaco-editor/pull/4087)
+
+## [0.40.0]
+
+- Support for Glyph Margin Widgets
+- Removes `getDiffLineInformationForOriginal` and `getDiffLineInformationForModified` from `IDiffEditor`
+- `createTrustedTypesPolicy` is optional now
+- New option `IModelDecorationOptions.shouldFillLineOnLineBreak`
+- New option `EditorOptions.readOnlyMessage`
+
+## [0.39.0]
+
+- New method `Environment.createTrustedTypesPolicy` to override trusted types handling.
+- Bugfixes
+
+Contributions to `monaco-editor`:
+
+- [@dlitsman (Dmitry Litsman)](https://github.com/dlitsman): Extend the "Rendering Glyphs In The Margin" example to include a transparent color note. [PR #3945](https://github.com/microsoft/monaco-editor/pull/3945)
+- [@dneto0 (David Neto)](https://github.com/dneto0): Avoid a hack in the WGSL lexer [PR #3887](https://github.com/microsoft/monaco-editor/pull/3887)
+- [@spahnke (Sebastian Pahnke)](https://github.com/spahnke)
+  - [JS, TS] Add Monarch support for private identifiers [PR #3919](https://github.com/microsoft/monaco-editor/pull/3919)
+  - [JS] Add static keyword [PR #3922](https://github.com/microsoft/monaco-editor/pull/3922)
+- [@titouanmathis (Titouan Mathis)](https://github.com/titouanmathis): [Webpack Plugin] Fix CJS being injected in ESM files [PR #3933](https://github.com/microsoft/monaco-editor/pull/3933)
+
+## [0.38.0]
+
+- `diffAlgorithm` values changed: `smart` -> `legacy`, `experimental` -> `advanced`
+- New `registerEditorOpener` API
+- New property `IViewZone.showInHiddenAreas` to show view zones in hidden areas
+- New properties `InlineCompletions.suppressSuggestions` and `InlineCompletions.enableForwardStability`
+- Bugfixes
+
+Contributions to `monaco-editor`:
+
+- [@dneto0 (David Neto)](https://github.com/dneto0): Add WebGPU Shading Language tokenizer, with tests [PR #3884](https://github.com/microsoft/monaco-editor/pull/3884)
+- [@kisstkondoros (Tamas Kiss)](https://github.com/kisstkondoros): Fix reference error in convert method of OutlineAdapter [PR #3924](https://github.com/microsoft/monaco-editor/pull/3924)
+- [@tamayika](https://github.com/tamayika): Change moduleResolution to node16 and adopt TS 5.0 [PR #3860](https://github.com/microsoft/monaco-editor/pull/3860)
+
 ## [0.37.1]
 ## [0.37.1]
 
 
 - Fixes Inline Completions feature
 - Fixes Inline Completions feature

+ 23 - 19
MAINTAINING.md

@@ -2,29 +2,26 @@
 
 
 (For maintainers only)
 (For maintainers only)
 
 
-- [P1 Inbox Queue](https://github.com/microsoft/monaco-editor/issues?q=is%3Aissue+is%3Aopen+-label%3Afeature-request+-label%3Aquestion+-label%3Aupstream+-label%3A%22help+wanted%22+-label%3A%22info-needed%22+-label%3A%22as-designed%22+-label%3Abug+-label%3A*question+)
-- [Inbox Queue](https://github.com/microsoft/monaco-editor/issues?q=is%3Aissue+is%3Aopen+no%3Aassignee+-label%3Afeature-request+-label%3Aquestion+-label%3Aupstream+-label%3A%22help+wanted%22+-label%3A%22info-needed%22+-label%3A%22as-designed%22+)
+Make sure every unassigned issue is labeled properly:
 
 
-## Updating TypeScript
+- [Inbox Queue](https://github.com/microsoft/monaco-editor/issues?q=is%3Aissue+is%3Aopen+no%3Aassignee+-label%3Afeature-request+-label%3Aupstream+-label%3A%22info-needed%22++-label%3Abug+)
 
 
-- change typescript's version in `package.json`.
-- execute `npm install .`
-- execute `npm run import-typescript`
-- adopt new APIs
-
-## Shipping a new monaco-editor npm module
+## Publishing a stable build monaco-editor build
 
 
-- update `package.json` and bump `"version"` as necessary
-- update `package.json` and edit `"vscode"` to point to the vscode repo commit that should be shipped at `monaco-editor-core` (both `monaco-editor-core` and `monaco-editor` will be published under the same version defined in `package.json`).
-- write entry in `CHANGELOG.md`
-  - API Changes / Breaking Changes / New and noteworthy
-  - Thank you ([use this tool](https://vscode-tools.azurewebsites.net/acknowledgement/))
-- trigger a build using [`Publish to npm`](https://github.com/microsoft/monaco-editor/actions/workflows/publish.yml) and type false when asked "is nightly?"
-- if the publish succeeded, run `git tag 0.x.y` and `git push origin 0.x.y`
-- edit `package.json` and update the `"monaco-editor-core"` dev dependency.
-- run `npm install`
+- Make sure there exists a nightly build from the VS Code commit the stable build should be built from
+- [Compare Last Stable With Nightly](https://microsoft.github.io/monaco-editor/playground.html?source=v0.40.0-dev.20230704#XQAAAAJWBgAAAAAAAABBqQkHQ5NjdMjwa-jY7SIQ9S7DNlzs5W-mwj0fe1ZCDRFc9ws9XQE0SJE1jc2VKxhaLFIw9vEWSxW3yscw_SM66BuzMt6m3zM8Thvb-XSMR_Da8IdBq3FOgly-7-xuaHSi_yUg58ZO9Mr-RKT7GyHzHoU8B9N7P-uTzmCdhT2Vv-4gNRbWSMQCUPrfmzFCkSH_WR2Vc8LGx2m0uRSFiJu82B1mS0RM-eriU9PTOqAgBrlPUMTU44VrHyVOqgs5BFrUuUHwGDzUHxeNuUk-kg2u70awQLQ83wD4o2EbSefqfIWkk2Yi0mnUS903tLA4V17MD_6OHIRArunMPL6E14ZCW0_Aql21F62Fmz--i_pNbqBIpSlBbZl6LzA1HzNsoDH7i2rn1qAw55L1MjwOU4QQMCJfffmJznAbGoZWkXK91OPYlOGNHNGG-MPUFsY5JSjLfvCWOvXypW9ZVkBZMo1qUbtE135CLqbaBiw52f3eOPBTru3IL_wT__ciAFI5NDiVOeN8V9zqkzbwiFNeQyZcjxmrDLjYTPJpao0dG61Um0w4FpVud8p77bjoAdEfG8JNO97W4cawj0HvMfvcZS81P7IsijZqA7KyVsdq79iCJQuMO31aS86cM4GTNT0TvdI7p62uiEmm9X6ZjF8oSLxW87Vt0oYAZ5wBePqdN6FwNO6BWACt2Ep9i5Q6h-mOy7_JWOiPTOH0Zz3v6SaNhjxJwZAqNG3FqvRTgLg-au-pfa8PD0No3U15UyWeqrVXSthGFghLJ16ppEwFCqFfQ6Vr0leZtSZXyk41-t5ZKMG-KQjzq1XE2PnuyOz60nV4GaYvGlMHrXz-XrEqb2kwNf_pBee0)
+  - Update [package.json](./package.json)
+    - set `version` to next stable
+    - set `vscodeRef` to _vscodeCommitId_
+    - update `devDependencies.monaco-editor-core` to _version_
+  - Run `npm install` to update lockfile
+  - Update [CHANGELOG.md](./CHANGELOG.md)
+    - API Changes / Breaking Changes / New and noteworthy
+    - Thank you ([use this tool](https://tools.code.visualstudio.com/acknowledgement))
+  - Commit
+  - [Trigger build](https://dev.azure.com/monacotools/Monaco/_build?definitionId=416)
 
 
-#### 8. Publish new webpack plugin
+#### Publish new webpack plugin
 
 
 - **TBD**
 - **TBD**
 - https://github.com/microsoft/monaco-editor/tree/main/webpack-plugin
 - https://github.com/microsoft/monaco-editor/tree/main/webpack-plugin
@@ -41,3 +38,10 @@
   - use `npm version major`
   - use `npm version major`
   - publish using `npm publish`
   - publish using `npm publish`
 - remember to push tags upstream
 - remember to push tags upstream
+
+## Updating TypeScript
+
+- change typescript's version in `package.json`.
+- execute `npm install .`
+- execute `npm run import-typescript`
+- adopt new APIs

+ 27 - 1
README.md

@@ -31,13 +31,39 @@ You will get:
 
 
 It is recommended to develop against the `dev` version, and in production to use the `min` version.
 It is recommended to develop against the `dev` version, and in production to use the `min` version.
 
 
+## Concepts
+
+Monaco editor is best known for being the text editor that powers VS Code. However, it's a bit more nuanced. Some basic understanding about the underlying concepts is needed to use Monaco editor effectively.
+
+### Models
+
+Models are at the heart of Monaco editor. It's what you interact with when managing content. A model represents a file that has been opened. This could represent a file that exists on a file system, but it doesn't have to. For example, the model holds the text content, determines the language of the content, and tracks the edit history of the content.
+
+### URIs
+
+Each model is identified by a URI. This is why it's not possible for two models to have the same URI. Ideally when you represent content in Monaco editor, you should think of a virtual file system that matches the files your users are editing. For example, you could use `file:///` as a base path. If a model is created without a URI, its URI will be `inmemory://model/1`. The number increases as more models are created.
+
+### Editors
+
+An editor is a user facing view of the model. This is what gets attached to the DOM and what your users see visually. Typical editor operations are displaying a model, managing the view state, or executing actions or commands.
+
+### Providers
+
+Providers provide smart editor features. For example, this includes completion and hover information. It is not the same as, but often maps to [language server protocol](https://microsoft.github.io/language-server-protocol) features.
+
+Providers work on models. Some smart features depends on the file URI. For example, for TypeScript to resolve imports, or for JSON IntelliSense to determine which JSON schema to apply to which model. So it's important to choose proper model URIs.
+
+### Disposables
+
+Many Monaco related objects often implement the `.dispose()` method. This method is intended to perform cleanups when a resource is no longer needed. For example, calling `model.dispose()` will unregister it, freeing up the URI for a new model. Editors should be disposed to free up resources and remove their model listeners.
+
 ## Documentation
 ## Documentation
 
 
 - Learn how to integrate the editor with these [complete samples](./samples/).
 - Learn how to integrate the editor with these [complete samples](./samples/).
   - [Integrate the AMD version](./docs/integrate-amd.md).
   - [Integrate the AMD version](./docs/integrate-amd.md).
   - [Integrate the ESM version](./docs/integrate-esm.md)
   - [Integrate the ESM version](./docs/integrate-esm.md)
 - Learn how to use the editor API and try out your own customizations in the [playground](https://microsoft.github.io/monaco-editor/playground.html).
 - Learn how to use the editor API and try out your own customizations in the [playground](https://microsoft.github.io/monaco-editor/playground.html).
-- Explore the [API docs](https://microsoft.github.io/monaco-editor/docs.html) or read them straight from [`monaco.d.ts`](https://github.com/microsoft/monaco-editor/blob/main/website/typedoc/monaco.d.ts).
+- Explore the [API docs](https://microsoft.github.io/monaco-editor/docs.html) or read them straight from [`monaco.d.ts`](https://microsoft.github.io/monaco-editor/node_modules/monaco-editor/monaco.d.ts).
 - Read [this guide](https://github.com/microsoft/monaco-editor/wiki/Accessibility-Guide-for-Integrators) to ensure the editor is accessible to all your users!
 - Read [this guide](https://github.com/microsoft/monaco-editor/wiki/Accessibility-Guide-for-Integrators) to ensure the editor is accessible to all your users!
 - Create a Monarch tokenizer for a new programming language [in the Monarch playground](https://microsoft.github.io/monaco-editor/monarch.html).
 - Create a Monarch tokenizer for a new programming language [in the Monarch playground](https://microsoft.github.io/monaco-editor/monarch.html).
 - Ask questions on [StackOverflow](https://stackoverflow.com/questions/tagged/monaco-editor)! Search open and closed issues, there are a lot of tips in there!
 - Ask questions on [StackOverflow](https://stackoverflow.com/questions/tagged/monaco-editor)! Search open and closed issues, there are a lot of tips in there!

+ 10 - 0
build/importTypescript.ts

@@ -37,6 +37,16 @@ export const typescriptVersion = "${typeScriptDependencyVersion}";\n`
 
 
 	let tsServices = fs.readFileSync(path.join(TYPESCRIPT_LIB_SOURCE, 'typescript.js')).toString();
 	let tsServices = fs.readFileSync(path.join(TYPESCRIPT_LIB_SOURCE, 'typescript.js')).toString();
 
 
+	tsServices = tsServices
+		.replace(
+			'const path = matchedStar ? subst.replace("*", matchedStar) : subst;',
+			'const path = matchedStar ? subst.replace("*", matchedStar) : subst; // CodeQL [SM02383] This is a false positive, the code is from the TypeScript compiler'
+		)
+		.replace(
+			'return key.replace("*", matchedStar);',
+			'return key.replace("*", matchedStar); // CodeQL [SM02383] This is a false positive, the code is from the TypeScript compiler'
+		);
+
 	// The output from this build will only be accessible via ESM; rather than removing
 	// The output from this build will only be accessible via ESM; rather than removing
 	// references to require/module, define them as dummy variables that bundlers will ignore.
 	// references to require/module, define them as dummy variables that bundlers will ignore.
 	// The TS code can figure out that it's not running under Node even with these defined.
 	// The TS code can figure out that it's not running under Node even with these defined.

+ 0 - 102
build/simpleserver.ts

@@ -10,8 +10,6 @@ import yaserver = require('yaserver');
 import { REPO_ROOT } from './utils';
 import { REPO_ROOT } from './utils';
 import { ensureDir } from './fs';
 import { ensureDir } from './fs';
 
 
-const WEBSITE_GENERATED_PATH = path.join(REPO_ROOT, 'website/playground/new-samples');
-
 generateTestSamplesTask();
 generateTestSamplesTask();
 
 
 const SERVER_ROOT = path.normalize(path.join(REPO_ROOT, '../'));
 const SERVER_ROOT = path.normalize(path.join(REPO_ROOT, '../'));
@@ -53,106 +51,6 @@ function generateTestSamplesTask() {
 	const destination = path.join(REPO_ROOT, 'test/manual/generated/all-samples.js');
 	const destination = path.join(REPO_ROOT, 'test/manual/generated/all-samples.js');
 	ensureDir(path.dirname(destination));
 	ensureDir(path.dirname(destination));
 	fs.writeFileSync(destination, prefix + JSON.stringify(samples, null, '\t') + suffix);
 	fs.writeFileSync(destination, prefix + JSON.stringify(samples, null, '\t') + suffix);
-
-	/** @type {{ chapter: string; name: string; id: string; path: string; }[]} */
-	const PLAY_SAMPLES = require(path.join(WEBSITE_GENERATED_PATH, 'all.js')).PLAY_SAMPLES;
-	/** @type {{ path: string; name: string; }[]} */
-	const locations = [];
-	for (let i = 0; i < PLAY_SAMPLES.length; i++) {
-		const sample = PLAY_SAMPLES[i];
-		const sampleId = sample.id;
-		const samplePath = path.join(WEBSITE_GENERATED_PATH, sample.path);
-
-		const html = fs.readFileSync(path.join(samplePath, 'sample.html'));
-		const js = fs.readFileSync(path.join(samplePath, 'sample.js'));
-		const css = fs.readFileSync(path.join(samplePath, 'sample.css'));
-
-		const result = [
-			'<!DOCTYPE html>',
-			'<!-- THIS IS A GENERATED FILE VIA `npm run simpleserver` -->',
-			'<html>',
-			'<head>',
-			'	<base href="../..">',
-			'	<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />',
-			'</head>',
-			'<body>',
-			'<style>',
-			'/*----------------------------------------SAMPLE CSS START*/',
-			'',
-			css,
-			'',
-			'/*----------------------------------------SAMPLE CSS END*/',
-			'</style>',
-			'<a class="loading-opts" href="generated/playground/index.html">[&lt;&lt; BACK]</a> <br/>',
-			'THIS IS A GENERATED FILE VIA `npm run simpleserver`',
-			'',
-			'<div id="bar" style="margin-bottom: 6px;"></div>',
-			'',
-			'<div style="clear:both"></div>',
-			'<div id="outer-container" style="width:800px;height:450px;border: 1px solid grey">',
-			'<!-- ----------------------------------------SAMPLE HTML START-->',
-			'',
-			html,
-			'',
-			'<!-- ----------------------------------------SAMPLE HTML END-->',
-			'</div>',
-			'<div style="clear:both"></div>',
-			'',
-			'<script src="dev-setup.js"></script>',
-			'<script>',
-			'loadEditor(function() {',
-			'/*----------------------------------------SAMPLE JS START*/',
-			'',
-			js,
-			'',
-			'/*----------------------------------------SAMPLE JS END*/',
-			'});',
-			'</script>',
-			'</body>',
-			'</html>'
-		];
-
-		const destination = path.join(
-			REPO_ROOT,
-			'test/manual/generated/playground/' + sampleId + '.html'
-		);
-		ensureDir(path.dirname(destination));
-		fs.writeFileSync(destination, result.join('\n'));
-		locations.push({
-			path: sampleId + '.html',
-			name: sample.chapter + ' &gt; ' + sample.name
-		});
-	}
-
-	const index = [
-		'<!DOCTYPE html>',
-		'<!-- THIS IS A GENERATED FILE VIA `npm run simpleserver` -->',
-		'<html>',
-		'<head>',
-		'	<base href="../..">',
-		'</head>',
-		'<body>',
-		'<a class="loading-opts" href="index.html">[&lt;&lt; BACK]</a><br/>',
-		'THIS IS A GENERATED FILE VIA `npm run simpleserver`<br/><br/>',
-		locations
-			.map(function (location) {
-				return (
-					'<a class="loading-opts" href="generated/playground/' +
-					location.path +
-					'">' +
-					location.name +
-					'</a>'
-				);
-			})
-			.join('<br/>\n'),
-		'<script src="dev-setup.js"></script>',
-		'</body>',
-		'</html>'
-	];
-	fs.writeFileSync(
-		path.join(REPO_ROOT, 'test/manual/generated/playground/index.html'),
-		index.join('\n')
-	);
 }
 }
 
 
 function createSimpleServer(rootDir: string, port: number) {
 function createSimpleServer(rootDir: string, port: number) {

文件差異過大導致無法顯示
+ 559 - 141
package-lock.json


+ 4 - 4
package.json

@@ -1,7 +1,7 @@
 {
 {
 	"name": "monaco-editor",
 	"name": "monaco-editor",
-	"version": "0.37.1",
-	"vscodeRef": "8f74fbfd1f2d8f6268a42df131726b218aafe511",
+	"version": "0.43.0",
+	"vscodeRef": "16e9205452dd417e32f164325b589b94892846b4",
 	"private": true,
 	"private": true,
 	"description": "A browser based code editor",
 	"description": "A browser based code editor",
 	"homepage": "https://github.com/microsoft/monaco-editor",
 	"homepage": "https://github.com/microsoft/monaco-editor",
@@ -52,7 +52,7 @@
 		"jsdom": "^19.0.0",
 		"jsdom": "^19.0.0",
 		"jsonc-parser": "^3.0.0",
 		"jsonc-parser": "^3.0.0",
 		"mocha": "^9.2.0",
 		"mocha": "^9.2.0",
-		"monaco-editor-core": "^0.37.0-dev.20230403",
+		"monaco-editor-core": "0.42.0-rc1",
 		"parcel": "^2.7.0",
 		"parcel": "^2.7.0",
 		"pin-github-action": "^1.8.0",
 		"pin-github-action": "^1.8.0",
 		"playwright": "^1.32.2",
 		"playwright": "^1.32.2",
@@ -64,7 +64,7 @@
 		"terser": "^5.14.2",
 		"terser": "^5.14.2",
 		"ts-node": "^10.6.0",
 		"ts-node": "^10.6.0",
 		"typescript": "^5.0.2",
 		"typescript": "^5.0.2",
-		"vite": "^3.1.8",
+		"vite": "^3.2.7",
 		"vscode-css-languageservice": "5.4.1",
 		"vscode-css-languageservice": "5.4.1",
 		"vscode-html-languageservice": "4.2.4",
 		"vscode-html-languageservice": "4.2.4",
 		"vscode-json-languageservice": "4.2.1",
 		"vscode-json-languageservice": "4.2.1",

+ 9 - 9
samples/browser-esm-vite-react/package-lock.json

@@ -13,7 +13,7 @@
 				"react": "^17.0.2",
 				"react": "^17.0.2",
 				"react-dom": "^17.0.2",
 				"react-dom": "^17.0.2",
 				"typescript": "^5.0.2",
 				"typescript": "^5.0.2",
-				"vite": "^2.9.13"
+				"vite": "^2.9.16"
 			}
 			}
 		},
 		},
 		"node_modules/@ampproject/remapping": {
 		"node_modules/@ampproject/remapping": {
@@ -1372,15 +1372,15 @@
 			}
 			}
 		},
 		},
 		"node_modules/vite": {
 		"node_modules/vite": {
-			"version": "2.9.13",
-			"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.13.tgz",
-			"integrity": "sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==",
+			"version": "2.9.16",
+			"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz",
+			"integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==",
 			"dev": true,
 			"dev": true,
 			"dependencies": {
 			"dependencies": {
 				"esbuild": "^0.14.27",
 				"esbuild": "^0.14.27",
 				"postcss": "^8.4.13",
 				"postcss": "^8.4.13",
 				"resolve": "^1.22.0",
 				"resolve": "^1.22.0",
-				"rollup": "^2.59.0"
+				"rollup": ">=2.59.0 <2.78.0"
 			},
 			},
 			"bin": {
 			"bin": {
 				"vite": "bin/vite.js"
 				"vite": "bin/vite.js"
@@ -2298,16 +2298,16 @@
 			"dev": true
 			"dev": true
 		},
 		},
 		"vite": {
 		"vite": {
-			"version": "2.9.13",
-			"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.13.tgz",
-			"integrity": "sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw==",
+			"version": "2.9.16",
+			"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz",
+			"integrity": "sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==",
 			"dev": true,
 			"dev": true,
 			"requires": {
 			"requires": {
 				"esbuild": "^0.14.27",
 				"esbuild": "^0.14.27",
 				"fsevents": "~2.3.2",
 				"fsevents": "~2.3.2",
 				"postcss": "^8.4.13",
 				"postcss": "^8.4.13",
 				"resolve": "^1.22.0",
 				"resolve": "^1.22.0",
-				"rollup": "^2.59.0"
+				"rollup": ">=2.59.0 <2.78.0"
 			}
 			}
 		}
 		}
 	}
 	}

+ 1 - 1
samples/browser-esm-vite-react/package.json

@@ -15,6 +15,6 @@
 		"@types/react-dom": "^17.0.11",
 		"@types/react-dom": "^17.0.11",
 		"@vitejs/plugin-react": "^1.1.4",
 		"@vitejs/plugin-react": "^1.1.4",
 		"typescript": "^5.0.2",
 		"typescript": "^5.0.2",
-		"vite": "^2.9.13"
+		"vite": "^2.9.16"
 	}
 	}
 }
 }

+ 15 - 0
scripts/ci/env.ts

@@ -0,0 +1,15 @@
+interface Env {
+	VSCODE_REF: string;
+	PRERELEASE_VERSION: string;
+}
+
+export function getNightlyEnv(): Env {
+	const env: Env = process.env as any;
+	if (!env.PRERELEASE_VERSION) {
+		throw new Error(`Missing PRERELEASE_VERSION in process.env`);
+	}
+	if (!env.VSCODE_REF) {
+		throw new Error(`Missing VSCODE_REF in process.env`);
+	}
+	return env;
+}

+ 6 - 2
scripts/ci/monaco-editor-core-prepare.ts

@@ -1,6 +1,7 @@
 import { mkdir, rm } from 'fs/promises';
 import { mkdir, rm } from 'fs/promises';
 import { join, resolve } from 'path';
 import { join, resolve } from 'path';
 import { PackageJson, group, gitShallowClone, run, writeJsonFile, getNightlyVersion } from '../lib';
 import { PackageJson, group, gitShallowClone, run, writeJsonFile, getNightlyVersion } from '../lib';
+import { getNightlyEnv } from './env';
 
 
 const selfPath = __dirname;
 const selfPath = __dirname;
 const rootPath = join(selfPath, '..', '..');
 const rootPath = join(selfPath, '..', '..');
@@ -21,8 +22,11 @@ async function prepareMonacoEditorCoreReleaseStableOrNightly() {
 		version = monacoEditorPackageJson.version;
 		version = monacoEditorPackageJson.version;
 		ref = monacoEditorPackageJson.vscodeRef;
 		ref = monacoEditorPackageJson.vscodeRef;
 	} else if (arg === 'nightly') {
 	} else if (arg === 'nightly') {
-		version = getNightlyVersion(monacoEditorPackageJson.version);
-		ref = 'main';
+		version = getNightlyVersion(
+			monacoEditorPackageJson.version,
+			getNightlyEnv().PRERELEASE_VERSION
+		);
+		ref = getNightlyEnv().VSCODE_REF;
 	} else {
 	} else {
 		throw new Error('Invalid argument');
 		throw new Error('Invalid argument');
 	}
 	}

+ 5 - 1
scripts/ci/monaco-editor-prepare.ts

@@ -1,6 +1,7 @@
 import { readFile } from 'fs/promises';
 import { readFile } from 'fs/promises';
 import { join, resolve } from 'path';
 import { join, resolve } from 'path';
 import { PackageJson, getNightlyVersion, group, run, writeJsonFile, gitCommitId } from '../lib';
 import { PackageJson, getNightlyVersion, group, run, writeJsonFile, gitCommitId } from '../lib';
+import { getNightlyEnv } from './env';
 
 
 const selfPath = __dirname;
 const selfPath = __dirname;
 const rootPath = join(selfPath, '..', '..');
 const rootPath = join(selfPath, '..', '..');
@@ -23,7 +24,10 @@ async function prepareMonacoEditorReleaseStableOrNightly() {
 	if (arg === 'stable') {
 	if (arg === 'stable') {
 		version = monacoEditorPackageJson.version;
 		version = monacoEditorPackageJson.version;
 	} else if (arg === 'nightly') {
 	} else if (arg === 'nightly') {
-		version = getNightlyVersion(monacoEditorPackageJson.version);
+		version = getNightlyVersion(
+			monacoEditorPackageJson.version,
+			getNightlyEnv().PRERELEASE_VERSION
+		);
 	} else {
 	} else {
 		throw new Error('Invalid argument');
 		throw new Error('Invalid argument');
 	}
 	}

+ 5 - 2
scripts/lib/index.ts

@@ -73,14 +73,17 @@ export async function writeJsonFile(filePath: string, jsonData: unknown): Promis
 	await writeFile(filePath, JSON.stringify(jsonData, null, '\t') + '\n');
 	await writeFile(filePath, JSON.stringify(jsonData, null, '\t') + '\n');
 }
 }
 
 
-export function getNightlyVersion(version: string): string {
+export function getNightlyVersion(version: string, prerelease: string): string {
 	const pieces = version.split('.');
 	const pieces = version.split('.');
 	const minor = parseInt(pieces[1], 10);
 	const minor = parseInt(pieces[1], 10);
 	const date = new Date();
 	const date = new Date();
 	const yyyy = date.getUTCFullYear();
 	const yyyy = date.getUTCFullYear();
 	const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
 	const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
 	const dd = String(date.getUTCDate()).padStart(2, '0');
 	const dd = String(date.getUTCDate()).padStart(2, '0');
-	return `0.${minor + 1}.0-dev.${yyyy}${mm}${dd}`;
+
+	prerelease = prerelease.replace('${today}', `${yyyy}${mm}${dd}`);
+
+	return `0.${minor + 1}.0-${prerelease}`;
 }
 }
 
 
 export interface PackageJson {
 export interface PackageJson {

+ 33 - 0
src/basic-languages/elixir/elixir.test.ts

@@ -303,6 +303,17 @@ testTokenization('elixir', [
 			]
 			]
 		}
 		}
 	],
 	],
+	// Sigils (multi-letter uppercase)
+	[
+		{
+			line: '~DX/foo/',
+			tokens: [
+				{ startIndex: 0, type: 'sigil.delimiter.elixir' },
+				{ startIndex: 4, type: 'sigil.elixir' },
+				{ startIndex: 7, type: 'sigil.delimiter.elixir' }
+			]
+		}
+	],
 	// Sigils (no interpolation)
 	// Sigils (no interpolation)
 	[
 	[
 		{
 		{
@@ -314,6 +325,17 @@ testTokenization('elixir', [
 			]
 			]
 		}
 		}
 	],
 	],
+	// Sigils (multi-letter uppercase no interpolation)
+	[
+		{
+			line: '~WW/foo#{1}/',
+			tokens: [
+				{ startIndex: 0, type: 'sigil.delimiter.elixir' },
+				{ startIndex: 4, type: 'sigil.elixir' },
+				{ startIndex: 11, type: 'sigil.delimiter.elixir' }
+			]
+		}
+	],
 	// Sigils (modifiers)
 	// Sigils (modifiers)
 	[
 	[
 		{
 		{
@@ -325,6 +347,17 @@ testTokenization('elixir', [
 			]
 			]
 		}
 		}
 	],
 	],
+	// Sigils (multi-letter uppercase with modifiers)
+	[
+		{
+			line: '~DX/custom/az09',
+			tokens: [
+				{ startIndex: 0, type: 'sigil.delimiter.elixir' },
+				{ startIndex: 4, type: 'sigil.elixir' },
+				{ startIndex: 10, type: 'sigil.delimiter.elixir' }
+			]
+		}
+	],
 	// Module attributes
 	// Module attributes
 	[
 	[
 		{
 		{

+ 10 - 9
src/basic-languages/elixir/elixir.ts

@@ -333,7 +333,8 @@ export const language = <languages.IMonarchLanguage>{
 
 
 		// See https://elixir-lang.org/getting-started/sigils.html
 		// See https://elixir-lang.org/getting-started/sigils.html
 		// Sigils allow for typing values using their textual representation.
 		// Sigils allow for typing values using their textual representation.
-		// All sigils start with ~ followed by a letter indicating sigil type
+		// All sigils start with ~ followed by a letter or
+		// multi-letter uppercase starting at Elixir v1.15.0, indicating sigil type
 		// and then a delimiter pair enclosing the textual representation.
 		// and then a delimiter pair enclosing the textual representation.
 		// Optional modifiers are allowed after the closing delimiter.
 		// Optional modifiers are allowed after the closing delimiter.
 		// For instance a regular expressions can be written as:
 		// For instance a regular expressions can be written as:
@@ -353,16 +354,16 @@ export const language = <languages.IMonarchLanguage>{
 
 
 		sigils: [
 		sigils: [
 			[/~[a-z]@sigilStartDelimiter/, { token: '@rematch', next: '@sigil.interpol' }],
 			[/~[a-z]@sigilStartDelimiter/, { token: '@rematch', next: '@sigil.interpol' }],
-			[/~[A-Z]@sigilStartDelimiter/, { token: '@rematch', next: '@sigil.noInterpol' }]
+			[/~([A-Z]+)@sigilStartDelimiter/, { token: '@rematch', next: '@sigil.noInterpol' }]
 		],
 		],
 
 
 		sigil: [
 		sigil: [
-			[/~([a-zA-Z])\{/, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.{.}' }],
-			[/~([a-zA-Z])\[/, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.[.]' }],
-			[/~([a-zA-Z])\(/, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.(.)' }],
-			[/~([a-zA-Z])\</, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.<.>' }],
+			[/~([a-z]|[A-Z]+)\{/, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.{.}' }],
+			[/~([a-z]|[A-Z]+)\[/, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.[.]' }],
+			[/~([a-z]|[A-Z]+)\(/, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.(.)' }],
+			[/~([a-z]|[A-Z]+)\</, { token: '@rematch', switchTo: '@sigilStart.$S2.$1.<.>' }],
 			[
 			[
-				/~([a-zA-Z])(@sigilSymmetricDelimiter)/,
+				/~([a-z]|[A-Z]+)(@sigilSymmetricDelimiter)/,
 				{ token: '@rematch', switchTo: '@sigilStart.$S2.$1.$2.$2' }
 				{ token: '@rematch', switchTo: '@sigilStart.$S2.$1.$2.$2' }
 			]
 			]
 		],
 		],
@@ -475,7 +476,7 @@ export const language = <languages.IMonarchLanguage>{
 		// Fallback to the generic sigil by default
 		// Fallback to the generic sigil by default
 		'sigilStart.interpol': [
 		'sigilStart.interpol': [
 			[
 			[
-				/~([a-zA-Z])@sigilStartDelimiter/,
+				/~([a-z]|[A-Z]+)@sigilStartDelimiter/,
 				{
 				{
 					token: 'sigil.delimiter',
 					token: 'sigil.delimiter',
 					switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
 					switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
@@ -498,7 +499,7 @@ export const language = <languages.IMonarchLanguage>{
 
 
 		'sigilStart.noInterpol': [
 		'sigilStart.noInterpol': [
 			[
 			[
-				/~([a-zA-Z])@sigilStartDelimiter/,
+				/~([a-z]|[A-Z]+)@sigilStartDelimiter/,
 				{
 				{
 					token: 'sigil.delimiter',
 					token: 'sigil.delimiter',
 					switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
 					switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'

+ 59 - 0
src/basic-languages/javascript/javascript.test.ts

@@ -39,6 +39,65 @@ testTokenization('javascript', [
 		}
 		}
 	],
 	],
 
 
+	// identifiers
+	[
+		{
+			line: 'foo;',
+			tokens: [
+				{ startIndex: 0, type: 'identifier.js' },
+				{ startIndex: 3, type: 'delimiter.js' }
+			]
+		}
+	],
+
+	[
+		{
+			line: 'foo() { return 1; }',
+			tokens: [
+				{ startIndex: 0, type: 'identifier.js' },
+				{ startIndex: 3, type: 'delimiter.parenthesis.js' },
+				{ startIndex: 5, type: '' },
+				{ startIndex: 6, type: 'delimiter.bracket.js' },
+				{ startIndex: 7, type: '' },
+				{ startIndex: 8, type: 'keyword.js' },
+				{ startIndex: 14, type: '' },
+				{ startIndex: 15, type: 'number.js' },
+				{ startIndex: 16, type: 'delimiter.js' },
+				{ startIndex: 17, type: '' },
+				{ startIndex: 18, type: 'delimiter.bracket.js' }
+			]
+		}
+	],
+
+	[
+		{
+			line: '#foo;',
+			tokens: [
+				{ startIndex: 0, type: 'identifier.js' },
+				{ startIndex: 4, type: 'delimiter.js' }
+			]
+		}
+	],
+
+	[
+		{
+			line: '#foo() { return 1; }',
+			tokens: [
+				{ startIndex: 0, type: 'identifier.js' },
+				{ startIndex: 4, type: 'delimiter.parenthesis.js' },
+				{ startIndex: 6, type: '' },
+				{ startIndex: 7, type: 'delimiter.bracket.js' },
+				{ startIndex: 8, type: '' },
+				{ startIndex: 9, type: 'keyword.js' },
+				{ startIndex: 15, type: '' },
+				{ startIndex: 16, type: 'number.js' },
+				{ startIndex: 17, type: 'delimiter.js' },
+				{ startIndex: 18, type: '' },
+				{ startIndex: 19, type: 'delimiter.bracket.js' }
+			]
+		}
+	],
+
 	// Comments - single line
 	// Comments - single line
 	[
 	[
 		{
 		{

+ 1 - 0
src/basic-languages/javascript/javascript.ts

@@ -43,6 +43,7 @@ export const language = <languages.IMonarchLanguage>{
 		'null',
 		'null',
 		'return',
 		'return',
 		'set',
 		'set',
+		'static',
 		'super',
 		'super',
 		'switch',
 		'switch',
 		'symbol',
 		'symbol',

+ 24 - 0
src/basic-languages/mdx/mdx.contribution.ts

@@ -0,0 +1,24 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { registerLanguage } from '../_.contribution';
+
+declare var AMD: any;
+declare var require: any;
+
+registerLanguage({
+	id: 'mdx',
+	extensions: ['.mdx'],
+	aliases: ['MDX', 'mdx'],
+	loader: () => {
+		if (AMD) {
+			return new Promise((resolve, reject) => {
+				require(['vs/basic-languages/mdx/mdx'], resolve, reject);
+			});
+		} else {
+			return import('./mdx');
+		}
+	}
+});

+ 171 - 0
src/basic-languages/mdx/mdx.test.ts

@@ -0,0 +1,171 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { testTokenization } from '../test/testRunner';
+
+testTokenization(
+	['mdx', 'yaml'],
+	[
+		// headers
+		[
+			{
+				line: '# header 1',
+				tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
+			},
+			{
+				line: '## header 2',
+				tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
+			},
+			{
+				line: '### header 3',
+				tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
+			},
+			{
+				line: '#### header 4',
+				tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
+			},
+			{
+				line: '##### header 5',
+				tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
+			},
+			{
+				line: '###### header 6',
+				tokens: [{ startIndex: 0, type: 'keyword.mdx' }]
+			}
+		],
+
+		// Lists
+		[
+			{
+				line: '- apple',
+				tokens: [
+					{ startIndex: 0, type: 'keyword.mdx' },
+					{ startIndex: 1, type: 'white.mdx' },
+					{ startIndex: 2, type: '' }
+				]
+			},
+			{
+				line: '* pear',
+				tokens: [
+					{ startIndex: 0, type: 'keyword.mdx' },
+					{ startIndex: 1, type: 'white.mdx' },
+					{ startIndex: 2, type: '' }
+				]
+			},
+			{
+				line: '+ pineapple',
+				tokens: [
+					{ startIndex: 0, type: 'keyword.mdx' },
+					{ startIndex: 1, type: 'white.mdx' },
+					{ startIndex: 2, type: '' }
+				]
+			},
+			{
+				line: '1. orange',
+				tokens: [
+					{ startIndex: 0, type: 'number.mdx' },
+					{ startIndex: 2, type: 'white.mdx' },
+					{ startIndex: 3, type: '' }
+				]
+			}
+		],
+
+		// Frontmatter
+		[
+			{
+				line: '---',
+				tokens: [{ startIndex: 0, type: 'meta.content.mdx' }]
+			},
+			{
+				line: 'frontmatter: yaml',
+				tokens: [
+					{ startIndex: 0, type: 'type.yaml' },
+					{ startIndex: 11, type: 'operators.yaml' },
+					{ startIndex: 12, type: 'white.yaml' },
+					{ startIndex: 13, type: 'string.yaml' }
+				]
+			},
+			{
+				line: '---',
+				tokens: [{ startIndex: 0, type: 'meta.content.mdx' }]
+			}
+		],
+
+		// links
+		[
+			{
+				line: '[MDX](https://mdxjs.com)',
+				tokens: [
+					{ startIndex: 0, type: '' },
+					{ startIndex: 1, type: 'type.identifier.mdx' },
+					{ startIndex: 4, type: '' },
+					{ startIndex: 6, type: 'string.link.mdx' },
+					{ startIndex: 23, type: '' }
+				]
+			},
+			{
+				line: '[monaco][monaco]',
+				tokens: [
+					{ startIndex: 0, type: '' },
+					{ startIndex: 1, type: 'type.identifier.mdx' },
+					{ startIndex: 7, type: '' },
+					{ startIndex: 9, type: 'type.identifier.mdx' },
+					{ startIndex: 15, type: '' }
+				]
+			},
+			{
+				line: '[monaco][]',
+				tokens: [
+					{ startIndex: 0, type: '' },
+					{ startIndex: 1, type: 'type.identifier.mdx' },
+					{ startIndex: 9, type: '' }
+				]
+			},
+			{
+				line: '[monaco]',
+				tokens: [
+					{ startIndex: 0, type: '' },
+					{ startIndex: 1, type: 'type.identifier.mdx' },
+					{ startIndex: 7, type: '' }
+				]
+			},
+			{
+				line: '[monaco]: https://github.com/microsoft/monaco-editor',
+				tokens: [
+					{ startIndex: 0, type: '' },
+					{ startIndex: 1, type: 'type.identifier.mdx' },
+					{ startIndex: 7, type: '' },
+					{ startIndex: 10, type: 'string.link.mdx' }
+				]
+			}
+		],
+
+		// JSX
+		[
+			{
+				line: '<div>**child**</div>',
+				tokens: [
+					{ startIndex: 0, type: 'type.identifier.mdx' },
+					// This is incorrect. MDX children that start on the same line are JSX, not markdown
+					{ startIndex: 5, type: 'strong.mdx' },
+					{ startIndex: 14, type: 'type.identifier.mdx' }
+				]
+			},
+			{
+				line: '{console.log("This is JavaScript")}',
+				tokens: [
+					{ startIndex: 0, type: 'delimiter.bracket.mdx' },
+					{ startIndex: 1, type: 'identifier.js' },
+					{ startIndex: 8, type: 'delimiter.js' },
+					{ startIndex: 9, type: 'identifier.js' },
+					{ startIndex: 12, type: 'delimiter.parenthesis.js' },
+					{ startIndex: 13, type: 'string.js' },
+					{ startIndex: 33, type: 'delimiter.parenthesis.js' },
+					{ startIndex: 34, type: 'delimiter.bracket.mdx' }
+				]
+			}
+		]
+	]
+);

+ 163 - 0
src/basic-languages/mdx/mdx.ts

@@ -0,0 +1,163 @@
+/*---------------------------------------------------------------------------------------------
+ *	Copyright (c) Microsoft Corporation. All rights reserved.
+ *	Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { languages } from '../../fillers/monaco-editor-core';
+
+export const conf: languages.LanguageConfiguration = {
+	comments: {
+		blockComment: ['{/*', '*/}']
+	},
+	brackets: [['{', '}']],
+	autoClosingPairs: [
+		{ open: '"', close: '"' },
+		{ open: "'", close: "'" },
+		{ open: '“', close: '”' },
+		{ open: '‘', close: '’' },
+		{ open: '`', close: '`' },
+		{ open: '{', close: '}' },
+		{ open: '(', close: ')' },
+		{ open: '_', close: '_' },
+		{ open: '**', close: '**' },
+		{ open: '<', close: '>' }
+	],
+	onEnterRules: [
+		{
+			beforeText: /^\s*- .+/,
+			action: { indentAction: languages.IndentAction.None, appendText: '- ' }
+		},
+		{
+			beforeText: /^\s*\+ .+/,
+			action: { indentAction: languages.IndentAction.None, appendText: '+ ' }
+		},
+		{
+			beforeText: /^\s*\* .+/,
+			action: { indentAction: languages.IndentAction.None, appendText: '* ' }
+		},
+		{
+			beforeText: /^> /,
+			action: { indentAction: languages.IndentAction.None, appendText: '> ' }
+		},
+		{
+			beforeText: /<\w+/,
+			action: { indentAction: languages.IndentAction.Indent }
+		},
+		{
+			beforeText: /\s+>\s*$/,
+			action: { indentAction: languages.IndentAction.Indent }
+		},
+		{
+			beforeText: /<\/\w+>/,
+			action: { indentAction: languages.IndentAction.Outdent }
+		},
+		...Array.from({ length: 100 }, (_, index) => ({
+			beforeText: new RegExp(`^${index}\\. .+`),
+			action: { indentAction: languages.IndentAction.None, appendText: `${index + 1}. ` }
+		}))
+	]
+};
+
+export const language = <languages.IMonarchLanguage>{
+	defaultToken: '',
+	tokenPostfix: '.mdx',
+	control: /[!#()*+.[\\\]_`{}\-]/,
+	escapes: /\\@control/,
+
+	tokenizer: {
+		root: [
+			[/^---$/, { token: 'meta.content', next: '@frontmatter', nextEmbedded: 'yaml' }],
+			[/^\s*import/, { token: 'keyword', next: '@import', nextEmbedded: 'js' }],
+			[/^\s*export/, { token: 'keyword', next: '@export', nextEmbedded: 'js' }],
+			[/<\w+/, { token: 'type.identifier', next: '@jsx' }],
+			[/<\/?\w+>/, 'type.identifier'],
+			[
+				/^(\s*)(>*\s*)(#{1,6}\s)/,
+				[{ token: 'white' }, { token: 'comment' }, { token: 'keyword', next: '@header' }]
+			],
+			[/^(\s*)(>*\s*)([*+-])(\s+)/, ['white', 'comment', 'keyword', 'white']],
+			[/^(\s*)(>*\s*)(\d{1,9}\.)(\s+)/, ['white', 'comment', 'number', 'white']],
+			[/^(\s*)(>*\s*)(\d{1,9}\.)(\s+)/, ['white', 'comment', 'number', 'white']],
+			[/^(\s*)(>*\s*)(-{3,}|\*{3,}|_{3,})$/, ['white', 'comment', 'keyword']],
+			[/`{3,}(\s.*)?$/, { token: 'string', next: '@codeblock_backtick' }],
+			[/~{3,}(\s.*)?$/, { token: 'string', next: '@codeblock_tilde' }],
+			[
+				/`{3,}(\S+).*$/,
+				{ token: 'string', next: '@codeblock_highlight_backtick', nextEmbedded: '$1' }
+			],
+			[
+				/~{3,}(\S+).*$/,
+				{ token: 'string', next: '@codeblock_highlight_tilde', nextEmbedded: '$1' }
+			],
+			[/^(\s*)(-{4,})$/, ['white', 'comment']],
+			[/^(\s*)(>+)/, ['white', 'comment']],
+			{ include: 'content' }
+		],
+		content: [
+			[
+				/(\[)(.+)(]\()(.+)(\s+".*")(\))/,
+				['', 'string.link', '', 'type.identifier', 'string.link', '']
+			],
+			[/(\[)(.+)(]\()(.+)(\))/, ['', 'type.identifier', '', 'string.link', '']],
+			[/(\[)(.+)(]\[)(.+)(])/, ['', 'type.identifier', '', 'type.identifier', '']],
+			[/(\[)(.+)(]:\s+)(\S*)/, ['', 'type.identifier', '', 'string.link']],
+			[/(\[)(.+)(])/, ['', 'type.identifier', '']],
+			[/`.*`/, 'variable.source'],
+			[/_/, { token: 'emphasis', next: '@emphasis_underscore' }],
+			[/\*(?!\*)/, { token: 'emphasis', next: '@emphasis_asterisk' }],
+			[/\*\*/, { token: 'strong', next: '@strong' }],
+			[/{/, { token: 'delimiter.bracket', next: '@expression', nextEmbedded: 'js' }]
+		],
+		import: [[/'\s*(;|$)/, { token: 'string', next: '@pop', nextEmbedded: '@pop' }]],
+		expression: [
+			[/{/, { token: 'delimiter.bracket', next: '@expression' }],
+			[/}/, { token: 'delimiter.bracket', next: '@pop', nextEmbedded: '@pop' }]
+		],
+		export: [[/^\s*$/, { token: 'delimiter.bracket', next: '@pop', nextEmbedded: '@pop' }]],
+		jsx: [
+			[/\s+/, ''],
+			[/(\w+)(=)("(?:[^"\\]|\\.)*")/, ['attribute.name', 'operator', 'string']],
+			[/(\w+)(=)('(?:[^'\\]|\\.)*')/, ['attribute.name', 'operator', 'string']],
+			[/(\w+(?=\s|>|={|$))/, ['attribute.name']],
+			[/={/, { token: 'delimiter.bracket', next: '@expression', nextEmbedded: 'js' }],
+			[/>/, { token: 'type.identifier', next: '@pop' }]
+		],
+		header: [
+			[/.$/, { token: 'keyword', next: '@pop' }],
+			{ include: 'content' },
+			[/./, { token: 'keyword' }]
+		],
+		strong: [
+			[/\*\*/, { token: 'strong', next: '@pop' }],
+			{ include: 'content' },
+			[/./, { token: 'strong' }]
+		],
+		emphasis_underscore: [
+			[/_/, { token: 'emphasis', next: '@pop' }],
+			{ include: 'content' },
+			[/./, { token: 'emphasis' }]
+		],
+		emphasis_asterisk: [
+			[/\*(?!\*)/, { token: 'emphasis', next: '@pop' }],
+			{ include: 'content' },
+			[/./, { token: 'emphasis' }]
+		],
+		frontmatter: [[/^---$/, { token: 'meta.content', nextEmbedded: '@pop', next: '@pop' }]],
+		codeblock_highlight_backtick: [
+			[/\s*`{3,}\s*$/, { token: 'string', next: '@pop', nextEmbedded: '@pop' }],
+			[/.*$/, 'variable.source']
+		],
+		codeblock_highlight_tilde: [
+			[/\s*~{3,}\s*$/, { token: 'string', next: '@pop', nextEmbedded: '@pop' }],
+			[/.*$/, 'variable.source']
+		],
+		codeblock_backtick: [
+			[/\s*`{3,}\s*$/, { token: 'string', next: '@pop' }],
+			[/.*$/, 'variable.source']
+		],
+		codeblock_tilde: [
+			[/\s*~{3,}\s*$/, { token: 'string', next: '@pop' }],
+			[/.*$/, 'variable.source']
+		]
+	}
+};

+ 1 - 0
src/basic-languages/monaco.contribution.ts

@@ -39,6 +39,7 @@ import './lua/lua.contribution';
 import './liquid/liquid.contribution';
 import './liquid/liquid.contribution';
 import './m3/m3.contribution';
 import './m3/m3.contribution';
 import './markdown/markdown.contribution';
 import './markdown/markdown.contribution';
+import './mdx/mdx.contribution';
 import './mips/mips.contribution';
 import './mips/mips.contribution';
 import './msdax/msdax.contribution';
 import './msdax/msdax.contribution';
 import './mysql/mysql.contribution';
 import './mysql/mysql.contribution';

+ 1 - 1
src/basic-languages/st/st.contribution.ts

@@ -10,7 +10,7 @@ declare var require: any;
 
 
 registerLanguage({
 registerLanguage({
 	id: 'st',
 	id: 'st',
-	extensions: ['.st', '.iecst', '.iecplc', '.lc3lib'],
+	extensions: ['.st', '.iecst', '.iecplc', '.lc3lib', '.TcPOU', '.TcDUT', '.TcGVL', '.TcIO'],
 	aliases: ['StructuredText', 'scl', 'stl'],
 	aliases: ['StructuredText', 'scl', 'stl'],
 	loader: () => {
 	loader: () => {
 		if (AMD) {
 		if (AMD) {

+ 2 - 1
src/basic-languages/st/st.ts

@@ -172,7 +172,8 @@ export const language = <languages.IMonarchLanguage>{
 		'vendor',
 		'vendor',
 		'common_source',
 		'common_source',
 		'from',
 		'from',
-		'extends'
+		'extends',
+		'implements'
 	],
 	],
 
 
 	constant: ['false', 'true', 'null'],
 	constant: ['false', 'true', 'null'],

+ 33 - 0
src/basic-languages/twig/twig.test.ts

@@ -734,6 +734,15 @@ testTokenization(
 				tokens: [{ startIndex: 0, type: 'comment.twig' }]
 				tokens: [{ startIndex: 0, type: 'comment.twig' }]
 			}
 			}
 		],
 		],
+		[
+			{
+				line: 'test {# Hello World! #}',
+				tokens: [
+					{ startIndex: 0, type: '' },
+					{ startIndex: 5, type: 'comment.twig' }
+				]
+			}
+		],
 		[
 		[
 			{
 			{
 				line: '{#Hello World!#}',
 				line: '{#Hello World!#}',
@@ -860,6 +869,19 @@ testTokenization(
 				]
 				]
 			}
 			}
 		],
 		],
+		[
+			{
+				line: 'test {{ foo }}',
+				tokens: [
+					{ startIndex: 0, type: '' },
+					{ startIndex: 5, type: 'delimiter.twig' },
+					{ startIndex: 7, type: '' },
+					{ startIndex: 8, type: 'variable.twig' },
+					{ startIndex: 11, type: '' },
+					{ startIndex: 12, type: 'delimiter.twig' }
+				]
+			}
+		],
 		[
 		[
 			{
 			{
 				line: '{{ foo(42) }}',
 				line: '{{ foo(42) }}',
@@ -962,6 +984,17 @@ testTokenization(
 				]
 				]
 			}
 			}
 		],
 		],
+		[
+			{
+				line: 'test {% %}',
+				tokens: [
+					{ startIndex: 0, type: '' },
+					{ startIndex: 5, type: 'delimiter.twig' },
+					{ startIndex: 7, type: '' },
+					{ startIndex: 8, type: 'delimiter.twig' }
+				]
+			}
+		],
 		[
 		[
 			{
 			{
 				line: '{% for item in navigation %}',
 				line: '{% for item in navigation %}',

+ 1 - 1
src/basic-languages/twig/twig.ts

@@ -104,7 +104,7 @@ export const language = <languages.IMonarchLanguage>{
 			[/(<)((?:[\w\-]+:)?[\w\-]+)/, ['delimiter.html', { token: 'tag.html', next: '@otherTag' }]],
 			[/(<)((?:[\w\-]+:)?[\w\-]+)/, ['delimiter.html', { token: 'tag.html', next: '@otherTag' }]],
 			[/(<\/)((?:[\w\-]+:)?[\w\-]+)/, ['delimiter.html', { token: 'tag.html', next: '@otherTag' }]],
 			[/(<\/)((?:[\w\-]+:)?[\w\-]+)/, ['delimiter.html', { token: 'tag.html', next: '@otherTag' }]],
 			[/</, 'delimiter.html'],
 			[/</, 'delimiter.html'],
-			[/[^<]+/] // text
+			[/[^<{]+/] // text
 		],
 		],
 
 
 		/**
 		/**

+ 59 - 0
src/basic-languages/typescript/typescript.test.ts

@@ -39,6 +39,65 @@ testTokenization('typescript', [
 		}
 		}
 	],
 	],
 
 
+	// identifiers
+	[
+		{
+			line: 'foo;',
+			tokens: [
+				{ startIndex: 0, type: 'identifier.ts' },
+				{ startIndex: 3, type: 'delimiter.ts' }
+			]
+		}
+	],
+
+	[
+		{
+			line: 'foo() { return 1; }',
+			tokens: [
+				{ startIndex: 0, type: 'identifier.ts' },
+				{ startIndex: 3, type: 'delimiter.parenthesis.ts' },
+				{ startIndex: 5, type: '' },
+				{ startIndex: 6, type: 'delimiter.bracket.ts' },
+				{ startIndex: 7, type: '' },
+				{ startIndex: 8, type: 'keyword.ts' },
+				{ startIndex: 14, type: '' },
+				{ startIndex: 15, type: 'number.ts' },
+				{ startIndex: 16, type: 'delimiter.ts' },
+				{ startIndex: 17, type: '' },
+				{ startIndex: 18, type: 'delimiter.bracket.ts' }
+			]
+		}
+	],
+
+	[
+		{
+			line: '#foo;',
+			tokens: [
+				{ startIndex: 0, type: 'identifier.ts' },
+				{ startIndex: 4, type: 'delimiter.ts' }
+			]
+		}
+	],
+
+	[
+		{
+			line: '#foo() { return 1; }',
+			tokens: [
+				{ startIndex: 0, type: 'identifier.ts' },
+				{ startIndex: 4, type: 'delimiter.parenthesis.ts' },
+				{ startIndex: 6, type: '' },
+				{ startIndex: 7, type: 'delimiter.bracket.ts' },
+				{ startIndex: 8, type: '' },
+				{ startIndex: 9, type: 'keyword.ts' },
+				{ startIndex: 15, type: '' },
+				{ startIndex: 16, type: 'number.ts' },
+				{ startIndex: 17, type: 'delimiter.ts' },
+				{ startIndex: 18, type: '' },
+				{ startIndex: 19, type: 'delimiter.bracket.ts' }
+			]
+		}
+	],
+
 	// Comments - single line
 	// Comments - single line
 	[
 	[
 		{
 		{

+ 1 - 1
src/basic-languages/typescript/typescript.ts

@@ -227,7 +227,7 @@ export const language = {
 		common: [
 		common: [
 			// identifiers and keywords
 			// identifiers and keywords
 			[
 			[
-				/[a-z_$][\w$]*/,
+				/#?[a-z_$][\w$]*/,
 				{
 				{
 					cases: {
 					cases: {
 						'@keywords': 'keyword',
 						'@keywords': 'keyword',

+ 1 - 3
src/basic-languages/wgsl/wgsl.ts

@@ -377,7 +377,7 @@ export const language = <languages.IMonarchLanguage>{
 	predeclared_intrinsics,
 	predeclared_intrinsics,
 	operators,
 	operators,
 
 
-	symbols: /[!%&*+\-\.\/:;<=>^|_~]+/,
+	symbols: /[!%&*+\-\.\/:;<=>^|_~,]+/,
 
 
 	tokenizer: {
 	tokenizer: {
 		root: [
 		root: [
@@ -402,8 +402,6 @@ export const language = <languages.IMonarchLanguage>{
 			{ include: '@commentOrSpace' },
 			{ include: '@commentOrSpace' },
 			{ include: '@numbers' },
 			{ include: '@numbers' },
 
 
-			[/;:\./, 'delimiter'],
-			[/,/, 'delimiter'], // Hack: Should be in previous rule
 			[/[{}()\[\]]/, '@brackets'],
 			[/[{}()\[\]]/, '@brackets'],
 			['@', 'annotation', '@attribute'],
 			['@', 'annotation', '@attribute'],
 			[
 			[

+ 1 - 1
src/language/typescript/languageFeatures.ts

@@ -890,7 +890,7 @@ export class OutlineAdapter extends Adapter implements languages.DocumentSymbolP
 				range: this._textSpanToRange(model, item.spans[0]),
 				range: this._textSpanToRange(model, item.spans[0]),
 				selectionRange: this._textSpanToRange(model, item.spans[0]),
 				selectionRange: this._textSpanToRange(model, item.spans[0]),
 				tags: [],
 				tags: [],
-				children: item.childItems?.map((child) => convert(child, result.name)),
+				children: item.childItems?.map((child) => convert(child, item.text)),
 				containerName: containerLabel
 				containerName: containerLabel
 			};
 			};
 			return result;
 			return result;

+ 2 - 2
src/language/typescript/lib/typescriptServices.js

@@ -40937,7 +40937,7 @@ ${lanes.join("\n")}
         trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
         trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
       }
       }
       const resolved = forEach(paths[matchedPatternText], (subst) => {
       const resolved = forEach(paths[matchedPatternText], (subst) => {
-        const path = matchedStar ? subst.replace("*", matchedStar) : subst;
+        const path = matchedStar ? subst.replace("*", matchedStar) : subst; // CodeQL [SM02383] This is a false positive, the code is from the TypeScript compiler
         const candidate = normalizePath(combinePaths(baseDirectory, path));
         const candidate = normalizePath(combinePaths(baseDirectory, path));
         if (state.traceEnabled) {
         if (state.traceEnabled) {
           trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
           trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
@@ -44626,7 +44626,7 @@ ${lanes.join("\n")}
           for (const { ending, value } of candidates) {
           for (const { ending, value } of candidates) {
             if (value.length >= prefix.length + suffix.length && startsWith(value, prefix) && endsWith(value, suffix) && validateEnding({ ending, value })) {
             if (value.length >= prefix.length + suffix.length && startsWith(value, prefix) && endsWith(value, suffix) && validateEnding({ ending, value })) {
               const matchedStar = value.substring(prefix.length, value.length - suffix.length);
               const matchedStar = value.substring(prefix.length, value.length - suffix.length);
-              return key.replace("*", matchedStar);
+              return key.replace("*", matchedStar); // CodeQL [SM02383] This is a false positive, the code is from the TypeScript compiler
             }
             }
           }
           }
         } else if (some(candidates, (c) => c.ending !== 0 /* Minimal */ && pattern === c.value) || some(candidates, (c) => c.ending === 0 /* Minimal */ && pattern === c.value && validateEnding(c))) {
         } else if (some(candidates, (c) => c.ending !== 0 /* Minimal */ && pattern === c.value) || some(candidates, (c) => c.ending === 0 /* Minimal */ && pattern === c.value && validateEnding(c))) {

+ 6 - 4
src/language/typescript/ts.worker.ts

@@ -3,15 +3,17 @@
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *  Licensed under the MIT License. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
  *--------------------------------------------------------------------------------------------*/
 
 
-import * as edworker from 'monaco-editor-core/esm/vs/editor/editor.worker';
-import { ICreateData, create } from './tsWorker';
+import { initialize } from 'monaco-editor-core/esm/vs/editor/editor.worker';
+import * as ts from './lib/typescriptServices';
+import { ICreateData, TypeScriptWorker, create } from './tsWorker';
 import { worker } from '../../fillers/monaco-editor-core';
 import { worker } from '../../fillers/monaco-editor-core';
+import { libFileMap } from './lib/lib';
 
 
 self.onmessage = () => {
 self.onmessage = () => {
 	// ignore the first message
 	// ignore the first message
-	edworker.initialize((ctx: worker.IWorkerContext, createData: ICreateData) => {
+	initialize((ctx: worker.IWorkerContext, createData: ICreateData) => {
 		return create(ctx, createData);
 		return create(ctx, createData);
 	});
 	});
 };
 };
 
 
-export { create } from './tsWorker';
+export { TypeScriptWorker, create, initialize, libFileMap, ts };

+ 8 - 8
test/manual/dev-setup.js

@@ -6,8 +6,8 @@
 		'npm/dev': 'node_modules/monaco-editor-core/dev/vs',
 		'npm/dev': 'node_modules/monaco-editor-core/dev/vs',
 		'npm/min': 'node_modules/monaco-editor-core/min/vs',
 		'npm/min': 'node_modules/monaco-editor-core/min/vs',
 		built: '/vscode/out-monaco-editor-core/min/vs',
 		built: '/vscode/out-monaco-editor-core/min/vs',
-		releaseDev: 'release/dev/vs',
-		releaseMin: 'release/min/vs'
+		releaseDev: 'out/monaco-editor/dev/vs',
+		releaseMin: 'out/monaco-editor/min/vs'
 	};
 	};
 	const pluginPaths = {
 	const pluginPaths = {
 		src: 'out/amd',
 		src: 'out/amd',
@@ -35,11 +35,11 @@
 		div.style.padding = '5px 20px 5px 5px';
 		div.style.padding = '5px 20px 5px 5px';
 		div.style.zIndex = '1000';
 		div.style.zIndex = '1000';
 
 
-		div.innerHTML =
-			'<ul><li>' +
-			renderLoadingOptions(true) +
-			(isRelease ? '' : `</li><li>${renderLoadingOptions(false)}`) +
-			'</li></ul>';
+		div.innerHTML = // CodeQL [SM03712] This code is not deployed and serves as local test code. No risk of malicious input.
+			'<ul><li>' + // CodeQL [SM03712] This code is not deployed and serves as local test code. No risk of malicious input.
+			renderLoadingOptions(true) + // CodeQL [SM03712] This code is not deployed and serves as local test code. No risk of malicious input.
+			(isRelease ? '' : `</li><li>${renderLoadingOptions(false)}`) + // CodeQL [SM03712] This code is not deployed and serves as local test code. No risk of malicious input.
+			'</li></ul>'; // CodeQL [SM03712] This code is not deployed and serves as local test code. No risk of malicious input.
 
 
 		document.body.appendChild(div);
 		document.body.appendChild(div);
 
 
@@ -47,7 +47,7 @@
 		for (let i = 0; i < aElements.length; i++) {
 		for (let i = 0; i < aElements.length; i++) {
 			let aElement = aElements[i];
 			let aElement = aElements[i];
 			if (aElement.className === 'loading-opts') {
 			if (aElement.className === 'loading-opts') {
-				aElement.href += window.location.search;
+				aElement.href += window.location.search; // CodeQL [SM01507] This code is not deployed and serves as local test code. No risk of malicious input.
 			}
 			}
 		}
 		}
 	})();
 	})();

+ 5 - 11
test/manual/index.js

@@ -1,4 +1,4 @@
-/// <reference path="../../release/monaco.d.ts" />
+/// <reference path="../../out/monaco-editor/monaco.d.ts" />
 define(['require', './samples'], function (require, SAMPLES) {
 define(['require', './samples'], function (require, SAMPLES) {
 	const domutils = require('vs/base/browser/dom');
 	const domutils = require('vs/base/browser/dom');
 
 
@@ -20,16 +20,10 @@ define(['require', './samples'], function (require, SAMPLES) {
 		renderWhitespace: true
 		renderWhitespace: true
 	});
 	});
 
 
-	editor.addCommand(
-		{
-			ctrlCmd: true,
-			key: 'F9'
-		},
-		function (ctx, args) {
-			alert('Command Running!!');
-			console.log(ctx);
-		}
-	);
+	editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.F9, function (ctx, args) {
+		alert('Command Running!!');
+		console.log(ctx);
+	});
 
 
 	editor.addAction({
 	editor.addAction({
 		id: 'my-unique-id',
 		id: 'my-unique-id',

+ 2 - 2
webpack-plugin/package-lock.json

@@ -1,12 +1,12 @@
 {
 {
 	"name": "monaco-editor-webpack-plugin",
 	"name": "monaco-editor-webpack-plugin",
-	"version": "7.0.1",
+	"version": "7.1.0",
 	"lockfileVersion": 2,
 	"lockfileVersion": 2,
 	"requires": true,
 	"requires": true,
 	"packages": {
 	"packages": {
 		"": {
 		"": {
 			"name": "monaco-editor-webpack-plugin",
 			"name": "monaco-editor-webpack-plugin",
-			"version": "7.0.1",
+			"version": "7.1.0",
 			"license": "MIT",
 			"license": "MIT",
 			"dependencies": {
 			"dependencies": {
 				"loader-utils": "^2.0.2"
 				"loader-utils": "^2.0.2"

+ 1 - 1
webpack-plugin/package.json

@@ -1,6 +1,6 @@
 {
 {
 	"name": "monaco-editor-webpack-plugin",
 	"name": "monaco-editor-webpack-plugin",
-	"version": "7.0.1",
+	"version": "7.1.0",
 	"description": "A webpack plugin for the Monaco Editor",
 	"description": "A webpack plugin for the Monaco Editor",
 	"main": "out/index.js",
 	"main": "out/index.js",
 	"typings": "./out/index.d.ts",
 	"typings": "./out/index.d.ts",

+ 7 - 3
webpack-plugin/src/loaders/include.ts

@@ -30,8 +30,12 @@ export const pitch: PitchLoaderDefinitionFunction<ILoaderOptions> = function pit
 		...(globals
 		...(globals
 			? Object.keys(globals).map((key) => `self[${JSON.stringify(key)}] = ${globals[key]};`)
 			? Object.keys(globals).map((key) => `self[${JSON.stringify(key)}] = ${globals[key]};`)
 			: []),
 			: []),
-		...pre.map((include: any) => `require(${stringifyRequest(include)});`),
-		`module.exports = require(${stringifyRequest(`!!${remainingRequest}`)});`,
-		...post.map((include: any) => `require(${stringifyRequest(include)});`)
+		...pre.map((include: any) => `import ${stringifyRequest(include)};`),
+		`
+import * as monaco from ${stringifyRequest(`!!${remainingRequest}`)};
+export * from ${stringifyRequest(`!!${remainingRequest}`)};
+export default monaco;
+		`,
+		...post.map((include: any) => `import ${stringifyRequest(include)};`)
 	].join('\n');
 	].join('\n');
 };
 };

+ 92 - 0
website/index/samples/sample.mdx.txt

@@ -0,0 +1,92 @@
+---
+frontmatter: data
+yaml: true
+---
+
+[link](https://example.com)
+
+~~~
+aasd
+asd
+asd
+~~~
+
+# Hello MDX {1+2}
+
+import { MyComponent } from './MyComponent'
+
+This is **bold {'foo' + 1} text**
+
+This is _emphasis {'foo' + 1} text_
+
+This is *emphasis {'foo' + 1} text too*
+
+    This is an indented *code* block
+
+export function foo() {
+  console.log('asd', 1)
+  if(true) {
+    return 'yep'
+  }
+  return 'nope'
+}
+
+
+This is regular content
+
+- this is a list
+
+* this is also a list
+
++ me too!
+
+1. pizza
+2. fries
+3. ice cream
+
+----
+
+_________
+
+***\
+~~~css
+body {
+  color: red;
+}
+~~~
+
+> - this is a list
+>
+> * this is also a list
+>
+> + me too!
+>
+> 1. pizza
+> 2. fries
+> 3. ice cream
+>
+> ---
+>
+> _________
+>
+> ***
+>
+> ```css
+> body {
+>   color: red;
+> }
+> ```
+
+> This is a blockquote
+>
+>> This is a nested {'blockquote'}
+
+{'foo' + 1 + 2 + {} + 12}
+
+{/* this is a comment */}
+
+<MyComponent contenteditable className="text-green-700" id='foo' width={100 + 100}>
+
+  This is **also** markdown.
+
+</MyComponent>

+ 1 - 1
website/package.json

@@ -24,7 +24,7 @@
 		"mini-css-extract-plugin": "^2.6.1",
 		"mini-css-extract-plugin": "^2.6.1",
 		"mobx": "^5.15.4",
 		"mobx": "^5.15.4",
 		"mobx-react": "^6.2.2",
 		"mobx-react": "^6.2.2",
-		"monaco-editor": "^0.35.0",
+		"monaco-editor": "^0.42.0-dev-20230906",
 		"react": "^17.0.2",
 		"react": "^17.0.2",
 		"react-bootstrap": "^2.4.0",
 		"react-bootstrap": "^2.4.0",
 		"react-dom": "^17.0.2",
 		"react-dom": "^17.0.2",

+ 1 - 1
website/src/monaco-loader.ts

@@ -84,7 +84,7 @@ function loadScript(path: string): Promise<void> {
 		script.onload = () => res();
 		script.onload = () => res();
 		script.async = true;
 		script.async = true;
 		script.type = "text/javascript";
 		script.type = "text/javascript";
-		script.src = path;
+		script.src = path; // CodeQL [SM01507] This is safe because the runner (that allows for dynamic paths) runs in an isolated iframe. The hosting website uses a static path configuration. // CodeQL [SM03712] This is safe because the runner (that allows for dynamic paths) runs in an isolated iframe. The hosting website uses a static path configuration.
 		document.head.appendChild(script);
 		document.head.appendChild(script);
 	});
 	});
 }
 }

+ 46 - 5
website/src/runner/index.ts

@@ -4,7 +4,7 @@
  *--------------------------------------------------------------------------------------------*/
  *--------------------------------------------------------------------------------------------*/
 
 
 import { loadMonaco } from "../monaco-loader";
 import { loadMonaco } from "../monaco-loader";
-import { IMessage, IPreviewState } from "../shared";
+import { IMessageFromRunner, IMessageToRunner, IPreviewState } from "../shared";
 import "./style.scss";
 import "./style.scss";
 
 
 window.addEventListener("message", (event) => {
 window.addEventListener("message", (event) => {
@@ -14,14 +14,14 @@ window.addEventListener("message", (event) => {
 		console.error("not in sandbox");
 		console.error("not in sandbox");
 		return;
 		return;
 	}
 	}
-	const e = event.data as IMessage | { kind: undefined };
+	const e = event.data as IMessageToRunner | { kind: undefined };
 	if (e.kind === "initialize") {
 	if (e.kind === "initialize") {
 		initialize(e.state);
 		initialize(e.state);
 	} else if (e.kind === "update-css") {
 	} else if (e.kind === "update-css") {
 		const style = document.getElementById(
 		const style = document.getElementById(
 			"custom-style"
 			"custom-style"
 		) as HTMLStyleElement;
 		) as HTMLStyleElement;
-		style.innerHTML = e.css;
+		style.innerHTML = e.css; // CodeQL [SM03712] This is safe because the runner runs in an isolated iframe.
 	}
 	}
 });
 });
 
 
@@ -46,13 +46,15 @@ async function initialize(state: IPreviewState) {
 
 
 	const style = document.createElement("style");
 	const style = document.createElement("style");
 	style.id = "custom-style";
 	style.id = "custom-style";
-	style.innerHTML = state.css;
+	style.innerHTML = state.css; // CodeQL [SM03712] This is safe because the runner runs in an isolated iframe. This feature is essential to the functionality of the playground. // CodeQL [SM02688] This is safe because the runner runs in an isolated iframe. This feature is essential to the functionality of the playground.
 	document.body.appendChild(style);
 	document.body.appendChild(style);
 
 
 	document.body.innerHTML += state.html;
 	document.body.innerHTML += state.html;
 
 
+	const js = massageJs(state.js);
+
 	try {
 	try {
-		eval(state.js);
+		eval(js); // CodeQL [SM01632] This is safe because the runner runs in an isolated iframe. This feature is essential to the functionality of the playground. // CodeQL [SM02688] This is safe because the runner runs in an isolated iframe. This feature is essential to the functionality of the playground.
 	} catch (err) {
 	} catch (err) {
 		const pre = document.createElement("pre");
 		const pre = document.createElement("pre");
 		pre.appendChild(
 		pre.appendChild(
@@ -61,3 +63,42 @@ async function initialize(state: IPreviewState) {
 		document.body.insertBefore(pre, document.body.firstChild);
 		document.body.insertBefore(pre, document.body.firstChild);
 	}
 	}
 }
 }
+
+function sendMessageToParent(message: IMessageFromRunner) {
+	window.parent.postMessage(message, "*");
+}
+
+(globalThis as any).$sendMessageToParent = sendMessageToParent;
+
+(globalThis as any).$bindModelToCodeStr = function bindModel(
+	model: any,
+	codeStringName: string
+) {
+	model.onDidChangeContent(() => {
+		const value = model.getValue();
+		sendMessageToParent({
+			kind: "update-code-string",
+			codeStringName,
+			value,
+		});
+	});
+};
+
+function massageJs(js: string) {
+	/*
+	Alternate experimental syntax: // bind to code string: `editor.getModel()` -> codeString
+
+	const bindToCodeStringRegexp = /\/\/ bind to code string: `(.*?)` -> (.*?)(\n|$)/g;
+	js = js.replaceAll(bindToCodeStringRegexp, (match, p1, p2) => {
+		return `globalThis.bindModelToCodeStr(${p1}, ${JSON.stringify(p2)})\n`;
+	});
+	*/
+
+	const setFromRegexp = /\/*\Wset from `(.*?)`:\W*\//g;
+	for (const m of js.matchAll(setFromRegexp)) {
+		const p1 = m[1];
+		const target = JSON.stringify("set from `" + p1 + "`");
+		js += `\n try { globalThis.$bindModelToCodeStr(${p1}, ${target}); } catch (e) { console.error(e); }`;
+	}
+	return js;
+}

+ 12 - 2
website/src/shared.ts

@@ -5,7 +5,7 @@
 
 
 import { IMonacoSetup } from "./monaco-loader";
 import { IMonacoSetup } from "./monaco-loader";
 
 
-export type IMessage =
+export type IMessageToRunner =
 	| {
 	| {
 			kind: "initialize";
 			kind: "initialize";
 			state: IPreviewState;
 			state: IPreviewState;
@@ -15,6 +15,16 @@ export type IMessage =
 			css: string;
 			css: string;
 	  };
 	  };
 
 
+export type IMessageFromRunner =
+	| {
+			kind: "update-code-string";
+			codeStringName: string;
+			value: string;
+	  }
+	| {
+			kind: "reload";
+	  };
+
 export interface IPlaygroundProject {
 export interface IPlaygroundProject {
 	js: string;
 	js: string;
 	css: string;
 	css: string;
@@ -22,6 +32,6 @@ export interface IPlaygroundProject {
 }
 }
 
 
 export interface IPreviewState extends IPlaygroundProject {
 export interface IPreviewState extends IPlaygroundProject {
-	key: number;
+	reloadKey: number;
 	monacoSetup: IMonacoSetup;
 	monacoSetup: IMonacoSetup;
 }
 }

+ 40 - 0
website/src/website/components/Loader.tsx

@@ -0,0 +1,40 @@
+import * as React from "react";
+export class Loader<T> extends React.Component<
+	{ children: (value: T) => React.ReactChild; loader: () => Promise<T> },
+	{ value: T | undefined; hasValue: boolean }
+> {
+	constructor(props: any) {
+		super(props);
+		this.state = { value: undefined, hasValue: false };
+		if (!this.state.value) {
+			this.props.loader().then((value) => {
+				this.setState({
+					hasValue: true,
+					value,
+				});
+			});
+		}
+	}
+
+	render() {
+		if (!this.state.hasValue) {
+			return null;
+		}
+		return this.props.children(this.state.value!);
+	}
+}
+
+/**
+ * Decorates a component so that it only gets mounted when monaco is loaded.
+ */
+export function withLoader(
+	loader: () => Promise<void>
+): <TProps>(
+	Component: React.FunctionComponent<TProps> | React.ComponentClass<TProps>
+) => any {
+	return (Component) => {
+		return (props: any) => (
+			<Loader loader={loader}>{() => <Component {...props} />}</Loader>
+		);
+	};
+}

+ 1 - 0
website/src/website/components/monaco/MonacoEditor.tsx

@@ -249,6 +249,7 @@ export class MonacoDiffEditor extends React.Component<
 			minimap: { enabled: false },
 			minimap: { enabled: false },
 			automaticLayout: false,
 			automaticLayout: false,
 			theme: this.props.theme,
 			theme: this.props.theme,
+			originalEditable: true,
 		});
 		});
 		this.editor.setModel({
 		this.editor.setModel({
 			original: this.props.originalModel,
 			original: this.props.originalModel,

+ 1 - 0
website/src/website/components/monaco/MonacoLoader.tsx

@@ -26,6 +26,7 @@ export class MonacoLoader extends React.Component<
 		return this.props.children(this.state.monaco);
 		return this.props.children(this.state.monaco);
 	}
 	}
 }
 }
+
 /**
 /**
  * Decorates a component so that it only gets mounted when monaco is loaded.
  * Decorates a component so that it only gets mounted when monaco is loaded.
  */
  */

+ 91 - 0
website/src/website/data/home-samples/sample.mdx.txt

@@ -0,0 +1,91 @@
+---
+title: Hello!
+---
+
+import {Chart} from './chart.js'
+import population from './population.js'
+import {External} from './some/place.js'
+
+export const year = 2018
+export const pi = 3.14
+
+export function SomeComponent(props) {
+  const name = (props || {}).name || 'world'
+
+  return <div>
+    <p>Hi, {name}!</p>
+
+    <p>and some more things</p>
+  </div>
+}
+
+export function Local(props) {
+  return <span style={{color: 'red'}} {...props} />
+}
+
+# Last year’s snowfall
+
+In {year}, the snowfall was above average.
+It was followed by a warm spring which caused
+flood conditions in many of the nearby rivers.
+
+<Chart year={year} color="#fcb32c" />
+
+<div className="note">
+  > Some notable things in a block quote!
+</div>
+
+# Heading (rank 1)
+## Heading 2
+### 3
+#### 4
+##### 5
+###### 6
+
+> Block quote
+
+* Unordered
+* List
+
+1. Ordered
+2. List
+
+A paragraph, introducing a thematic break:
+
+---
+
+```js
+// Get an element.
+const element = document.querySelectorAll('#hi')
+
+// Add a class.
+element.classList.add('asd')
+```
+
+a [link](https://example.com), an ![image](./image.png), some *emphasis*,
+something **strong**, and finally a little `code()`.
+
+<Component
+  open
+  x={1}
+  label={'this is a string, *not* markdown!'}
+  icon={<Icon />}
+/>
+
+Two 🍰 is: {Math.PI * 2}
+
+{(function () {
+  const guess = Math.random()
+
+  if (guess > 0.66) {
+    return <span style={{color: 'tomato'}}>Look at us.</span>
+  }
+
+  if (guess > 0.33) {
+    return <span style={{color: 'violet'}}>Who would have guessed?!</span>
+  }
+
+  return <span style={{color: 'goldenrod'}}>Not me.</span>
+})()}
+
+{/* A comment! */}

+ 14 - 4
website/src/website/data/playground-samples/creating-the-diffeditor/hello-diff-world/sample.js

@@ -1,8 +1,18 @@
-var originalModel = monaco.editor.createModel("heLLo world!", "text/plain");
-var modifiedModel = monaco.editor.createModel("hello orlando!", "text/plain");
+const originalModel = monaco.editor.createModel(
+	/* set from `originalModel`: */ `hello world`,
+	"text/plain"
+);
+const modifiedModel = monaco.editor.createModel(
+	/* set from `modifiedModel`: */ `Hello World!`,
+	"text/plain"
+);
 
 
-var diffEditor = monaco.editor.createDiffEditor(
-	document.getElementById("container")
+const diffEditor = monaco.editor.createDiffEditor(
+	document.getElementById("container"),
+	{
+		originalEditable: true,
+		automaticLayout: true,
+	}
 );
 );
 diffEditor.setModel({
 diffEditor.setModel({
 	original: originalModel,
 	original: originalModel,

+ 3 - 3
website/src/website/data/playground-samples/creating-the-editor/hello-world/sample.js

@@ -1,10 +1,10 @@
-const text = `function hello() {
+const value = /* set from `myEditor.getModel()`: */ `function hello() {
 	alert('Hello world!');
 	alert('Hello world!');
 }`;
 }`;
 
 
 // Hover on each property to see its docs!
 // Hover on each property to see its docs!
-monaco.editor.create(document.getElementById("container"), {
-	value: text,
+const myEditor = monaco.editor.create(document.getElementById("container"), {
+	value,
 	language: "javascript",
 	language: "javascript",
 	automaticLayout: true,
 	automaticLayout: true,
 });
 });

+ 2 - 1
website/src/website/data/playground-samples/interacting-with-the-editor/rendering-glyphs-in-the-margin/sample.css

@@ -2,5 +2,6 @@
 	background: red;
 	background: red;
 }
 }
 .myContentClass {
 .myContentClass {
-	background: lightblue;
+	/* Make sure to use transparent colors for the selection to work */
+	background: rgba(173, 216, 230, 0.5);
 }
 }

+ 7 - 8
website/src/website/pages/home/Home.tsx

@@ -172,10 +172,9 @@ class EditorDemo extends React.Component {
 						<div>
 						<div>
 							<h2>IntelliSense, Validation</h2>
 							<h2>IntelliSense, Validation</h2>
 							<p>
 							<p>
-								Paragraph of text beneath the heading to explain
-								the heading. We'll add onto it with another
-								sentence and probably just keep going until we
-								run out of words.
+								Get completions and errors directly in the
+								browser for supported languages. Or write your
+								own completion providers in JavaScript.
 							</p>
 							</p>
 						</div>
 						</div>
 					</div>
 					</div>
@@ -184,10 +183,9 @@ class EditorDemo extends React.Component {
 						<div>
 						<div>
 							<h2>Basic Syntax Colorization</h2>
 							<h2>Basic Syntax Colorization</h2>
 							<p>
 							<p>
-								Paragraph of text beneath the heading to explain
-								the heading. We'll add onto it with another
-								sentence and probably just keep going until we
-								run out of words.
+								Colorize code using our pre-built syntax
+								highlighting, or configure your own custom
+								colorization.
 							</p>
 							</p>
 						</div>
 						</div>
 					</div>
 					</div>
@@ -217,6 +215,7 @@ class EditorDemo extends React.Component {
 						value={this.currentSample.value || "loading..."}
 						value={this.currentSample.value || "loading..."}
 						language={this.currentLanguage?.id}
 						language={this.currentLanguage?.id}
 						theme={this.currentTheme.id}
 						theme={this.currentTheme.id}
+						onDidValueChange={() => {}}
 					/>
 					/>
 				</div>
 				</div>
 			</div>
 			</div>

+ 161 - 0
website/src/website/pages/playground/BisectModel.ts

@@ -0,0 +1,161 @@
+import { action, ObservableMap } from "mobx";
+import {
+	getNpmVersions,
+	getNpmVersionsSync,
+	getVsCodeCommitId,
+} from "./getNpmVersionsSync";
+import { PlaygroundModel } from "./PlaygroundModel";
+import { findLastIndex } from "./utils";
+
+export class BisectModel {
+	private readonly map = new ObservableMap<string, boolean>();
+
+	constructor(private readonly model: PlaygroundModel) {}
+
+	public getState(version: string): boolean | undefined {
+		return this.map.get(version);
+	}
+
+	public get isActive() {
+		return [...this.map.values()].some((e) => e !== undefined);
+	}
+
+	public reset(): void {
+		this.map.clear();
+	}
+
+	public async toggleState(version: string, state: boolean): Promise<void> {
+		const currentState = this.getState(version);
+		await this.setState(
+			version,
+			currentState === state ? undefined : state
+		);
+	}
+
+	@action
+	public async setState(
+		version: string,
+		state: boolean | undefined
+	): Promise<void> {
+		if (state === undefined) {
+			this.map.delete(version);
+		} else {
+			this.map.set(version, state);
+		}
+
+		const nextVersion = await this.getNextVersion();
+		if (!nextVersion) {
+			return;
+		}
+		this.model.settings.setSettings({
+			...this.model.settings.settings,
+			npmVersion: nextVersion,
+		});
+	}
+
+	private get versions() {
+		return getNpmVersionsSync(undefined);
+	}
+
+	private get indexOfLastBadVersion() {
+		return findLastIndex(this.versions, (v) => this.map.get(v) === false);
+	}
+	private get indexOfFirstGoodVersion() {
+		return this.versions.findIndex((v) => this.map.get(v) === true);
+	}
+
+	public get steps() {
+		const indexOfFirstGoodVersion = this.indexOfFirstGoodVersion;
+		const indexOfLastBadVersion = this.indexOfLastBadVersion;
+
+		if (indexOfFirstGoodVersion === -1 && indexOfLastBadVersion === -1) {
+			return -1;
+		}
+		if (indexOfFirstGoodVersion === -1) {
+			return Math.ceil(
+				Math.log2(this.versions.length - indexOfLastBadVersion)
+			);
+		} else if (indexOfLastBadVersion === -1) {
+			return Math.ceil(Math.log2(indexOfFirstGoodVersion + 1));
+		} else {
+			return Math.ceil(
+				Math.log2(indexOfFirstGoodVersion - indexOfLastBadVersion)
+			);
+		}
+	}
+
+	public get isFinished() {
+		if (
+			this.indexOfFirstGoodVersion !== -1 &&
+			this.indexOfLastBadVersion + 1 === this.indexOfFirstGoodVersion
+		) {
+			return true;
+		}
+		return false;
+	}
+
+	public async openGithub() {
+		const versions = await getNpmVersions();
+		const indexOfFirstGoodVersion =
+			this.indexOfFirstGoodVersion === -1
+				? versions.length - 1
+				: this.indexOfFirstGoodVersion;
+		const indexOfLastBadVersion =
+			this.indexOfLastBadVersion === -1 ? 0 : this.indexOfLastBadVersion;
+		const goodCommitId = await getVsCodeCommitId(
+			versions[indexOfFirstGoodVersion]
+		);
+		const badCommitId = await getVsCodeCommitId(
+			versions[indexOfLastBadVersion]
+		);
+		window.open(
+			`https://github.com/microsoft/vscode/compare/${goodCommitId}...${badCommitId}`,
+			"_blank"
+		);
+	}
+
+	private async getNextVersion(): Promise<string | undefined> {
+		const versions = await getNpmVersions();
+
+		const indexOfFirstGoodVersion = this.indexOfFirstGoodVersion;
+		const indexOfLastBadVersion = this.indexOfLastBadVersion;
+
+		if (
+			indexOfFirstGoodVersion !== -1 &&
+			indexOfLastBadVersion + 1 === indexOfFirstGoodVersion
+		) {
+			// Finished
+			return;
+		}
+
+		if (indexOfLastBadVersion === -1 && indexOfFirstGoodVersion === -1) {
+			return versions[0];
+		}
+		if (indexOfLastBadVersion === -1) {
+			// try first (newest) version that hasn't been tested
+			const indexOfFirstUntestedVersion = versions.findIndex(
+				(v) => this.map.get(v) === undefined
+			);
+			if (indexOfFirstUntestedVersion === -1) {
+				return undefined;
+			}
+			return versions[indexOfFirstUntestedVersion];
+		}
+
+		if (indexOfFirstGoodVersion === -1) {
+			/*// exponential back off, might be good for recent regressions, but ruins step counter
+			const candidate = Math.min(
+				indexOfLastBadVersion * 2 + 1,
+				versions.length - 1
+			);*/
+			const candidate = Math.floor(
+				(indexOfLastBadVersion + versions.length) / 2
+			);
+			return versions[candidate];
+		}
+
+		return versions[
+			Math.floor((indexOfLastBadVersion + indexOfFirstGoodVersion) / 2)
+		];
+	}
+}

+ 211 - 0
website/src/website/pages/playground/LocationModel.ts

@@ -0,0 +1,211 @@
+import { action, observable } from "mobx";
+import { IPlaygroundProject } from "../../../shared";
+import { monacoEditorVersion } from "../../monacoEditorVersion";
+import { LzmaCompressor } from "../../utils/lzmaCompressor";
+import {
+	HistoryController,
+	IHistoryModel,
+	ILocation,
+} from "../../utils/ObservableHistory";
+import { debouncedComputed, Disposable } from "../../utils/utils";
+import { getPlaygroundExamples, PlaygroundExample } from "./playgroundExamples";
+import { Source } from "./Source";
+import { PlaygroundModel } from "./PlaygroundModel";
+import { projectEquals } from "./utils";
+
+export class LocationModel implements IHistoryModel {
+	public readonly dispose = Disposable.fn();
+
+	private readonly compressor = new LzmaCompressor<IPlaygroundProject>();
+
+	private cachedState:
+		| { state: IPlaygroundProject; hash: string }
+		| undefined = undefined;
+
+	@observable private _sourceOverride: Source | undefined;
+	get sourceOverride(): Source | undefined {
+		return this._sourceOverride;
+	}
+
+	@observable private _compareWith: Source | undefined;
+	get compareWith(): Source | undefined {
+		return this._compareWith;
+	}
+
+	/**
+	 * This is used to control replace/push state.
+	 * Replace is used if the history id does not change.
+	 */
+	@observable historyId: number = 0;
+
+	constructor(private readonly model: PlaygroundModel) {
+		this.dispose.track(
+			new HistoryController((initialLocation) => {
+				this.updateLocation(initialLocation);
+				return this;
+			})
+		);
+	}
+
+	get location(): ILocation {
+		const source = this._sourceOverride || this.sourceFromSettings;
+		return {
+			hashValue: this.computedHashValue.value || this.cachedState?.hash,
+			searchParams: {
+				source: source?.sourceToString(),
+				sourceLanguages: source?.sourceLanguagesToString(),
+				compareWith: this._compareWith?.sourceToString(),
+			},
+		};
+	}
+
+	@action
+	updateLocation(currentLocation: ILocation): void {
+		const hashValue = currentLocation.hashValue;
+		const sourceStr = currentLocation.searchParams.source;
+		const sourceLanguages = currentLocation.searchParams.sourceLanguages;
+		const source =
+			sourceStr || sourceLanguages
+				? Source.parse(sourceStr, sourceLanguages)
+				: undefined;
+
+		if (this.sourceFromSettings?.equals(source)) {
+			this._sourceOverride = undefined;
+		} else {
+			this._sourceOverride = source;
+		}
+
+		const compareWithStr = currentLocation.searchParams.compareWith;
+		const compareWith = compareWithStr
+			? Source.parse(compareWithStr, undefined)
+			: undefined;
+		this._compareWith = compareWith;
+
+		function findExample(hashValue: string): PlaygroundExample | undefined {
+			if (hashValue.startsWith("example-")) {
+				hashValue = hashValue.substring("example-".length);
+			}
+			return getPlaygroundExamples()
+				.flatMap((e) => e.examples)
+				.find((e) => e.id === hashValue);
+		}
+
+		let example: PlaygroundExample | undefined;
+
+		if (!hashValue) {
+			this.model.selectedExample = getPlaygroundExamples()[0].examples[0];
+		} else if ((example = findExample(hashValue))) {
+			this.model.selectedExample = example;
+		} else {
+			let p: IPlaygroundProject | undefined = undefined;
+			if (this.cachedState?.hash === hashValue) {
+				p = this.cachedState.state;
+			}
+			if (!p) {
+				try {
+					p =
+						this.compressor.decodeData<IPlaygroundProject>(
+							hashValue
+						);
+				} catch (e) {
+					console.log("Could not deserialize from hash value", e);
+				}
+			}
+			if (p) {
+				this.cachedState = { state: p, hash: hashValue };
+				this.model.setState(p);
+			}
+		}
+	}
+
+	private readonly computedHashValue = debouncedComputed(
+		500,
+		() => ({
+			state: this.model.playgroundProject,
+			selectedExampleProject: this.model.selectedExampleProject,
+		}),
+		({ state, selectedExampleProject }) => {
+			if (
+				selectedExampleProject &&
+				projectEquals(state, selectedExampleProject.project)
+			) {
+				return "example-" + selectedExampleProject.example.id;
+			}
+			if (
+				this.cachedState &&
+				projectEquals(this.cachedState.state, state)
+			) {
+				return this.cachedState.hash;
+			}
+			return this.compressor.encodeData(state);
+		}
+	);
+
+	private get sourceFromSettings(): Source | undefined {
+		const settings = this.model.settings.settings;
+		if (settings.monacoSource === "npm") {
+			return new Source(settings.npmVersion, undefined, undefined);
+		} else if (
+			settings.monacoSource === "independent" &&
+			((settings.coreSource === "url" &&
+				(settings.languagesSource === "latest" ||
+					settings.languagesSource === "url")) ||
+				(settings.coreSource === "latest" &&
+					settings.languagesSource === "url"))
+		) {
+			return new Source(
+				undefined,
+				settings.coreSource === "url" ? settings.coreUrl : undefined,
+				settings.languagesSource === "latest"
+					? undefined
+					: settings.languagesUrl
+			);
+		} else if (settings.monacoSource === "latest") {
+			return new Source(monacoEditorVersion, undefined, undefined);
+		}
+		return undefined;
+	}
+
+	@action
+	exitCompare(): void {
+		this._compareWith = undefined;
+		this.historyId++;
+	}
+
+	@action
+	disableSourceOverride(): void {
+		this._sourceOverride = undefined;
+		this.historyId++;
+	}
+
+	@action
+	compareWithLatestDev(): void {
+		this._compareWith = Source.useLatestDev();
+		this.historyId++;
+	}
+
+	@action
+	saveCompareWith(): void {
+		if (this._compareWith) {
+			this.model.settings.setSettings({
+				...this.model.settings.settings,
+				...this._compareWith.toPartialSettings(),
+			});
+			this.historyId++;
+			this._compareWith = undefined;
+			this._sourceOverride = undefined;
+		}
+	}
+
+	@action
+	saveSourceOverride(): void {
+		if (this._sourceOverride) {
+			this.model.settings.setSettings({
+				...this.model.settings.settings,
+				...this._sourceOverride.toPartialSettings(),
+			});
+			this.historyId++;
+			this._sourceOverride = undefined;
+		}
+	}
+}

+ 92 - 462
website/src/website/pages/playground/PlaygroundModel.ts

@@ -8,7 +8,6 @@ import {
 	autorun,
 	autorun,
 	computed,
 	computed,
 	observable,
 	observable,
-	ObservableMap,
 	reaction,
 	reaction,
 	runInAction,
 	runInAction,
 } from "mobx";
 } from "mobx";
@@ -18,22 +17,10 @@ import {
 	waitForLoadedMonaco,
 	waitForLoadedMonaco,
 } from "../../../monaco-loader";
 } from "../../../monaco-loader";
 import { IPlaygroundProject, IPreviewState } from "../../../shared";
 import { IPlaygroundProject, IPreviewState } from "../../../shared";
-import { monacoEditorVersion } from "../../monacoEditorVersion";
 import { Debouncer } from "../../utils/Debouncer";
 import { Debouncer } from "../../utils/Debouncer";
-import { LzmaCompressor } from "../../utils/lzmaCompressor";
-import {
-	HistoryController,
-	IHistoryModel,
-	ILocation,
-} from "../../utils/ObservableHistory";
 import { ObservablePromise } from "../../utils/ObservablePromise";
 import { ObservablePromise } from "../../utils/ObservablePromise";
-import { debouncedComputed, Disposable } from "../../utils/utils";
-import {
-	getNpmVersions,
-	getNpmVersionsSync,
-	getVsCodeCommitId,
-} from "./getNpmVersionsSync";
-import { getPlaygroundExamples, PlaygroundExample } from "./playgroundExamples";
+import { Disposable } from "../../utils/utils";
+import { PlaygroundExample } from "./playgroundExamples";
 import {
 import {
 	getDefaultSettings,
 	getDefaultSettings,
 	JsonString,
 	JsonString,
@@ -41,6 +28,8 @@ import {
 	SettingsModel,
 	SettingsModel,
 	toLoaderConfig,
 	toLoaderConfig,
 } from "./SettingsModel";
 } from "./SettingsModel";
+import { BisectModel } from "./BisectModel";
+import { LocationModel } from "./LocationModel";
 
 
 export class PlaygroundModel {
 export class PlaygroundModel {
 	public readonly dispose = Disposable.fn();
 	public readonly dispose = Disposable.fn();
@@ -58,16 +47,18 @@ export class PlaygroundModel {
 	@observable
 	@observable
 	public reloadKey = 0;
 	public reloadKey = 0;
 
 
-	public readonly serializer = new StateUrlSerializer(this);
+	public readonly historyModel = new LocationModel(this);
 
 
 	public reload(): void {
 	public reload(): void {
 		this.reloadKey++;
 		this.reloadKey++;
 	}
 	}
 
 
-	private readonly _previewHandlers = new Set<IPreviewHandler>();
+	public get previewShouldBeFullScreen(): boolean {
+		return this.settings.previewFullScreen;
+	}
 
 
 	private _wasEverNonFullScreen = false;
 	private _wasEverNonFullScreen = false;
-	public get wasEverNonFullScreen() {
+	public get wasEverNonFullScreen(): boolean {
 		if (this._wasEverNonFullScreen) {
 		if (this._wasEverNonFullScreen) {
 			return true;
 			return true;
 		}
 		}
@@ -79,7 +70,7 @@ export class PlaygroundModel {
 
 
 	@computed.struct
 	@computed.struct
 	get monacoSetup(): IMonacoSetup {
 	get monacoSetup(): IMonacoSetup {
-		const sourceOverride = this.serializer.sourceOverride;
+		const sourceOverride = this.historyModel.sourceOverride;
 		if (sourceOverride) {
 		if (sourceOverride) {
 			return toLoaderConfig({
 			return toLoaderConfig({
 				...getDefaultSettings(),
 				...getDefaultSettings(),
@@ -105,10 +96,33 @@ export class PlaygroundModel {
 		return {
 		return {
 			...this.playgroundProject,
 			...this.playgroundProject,
 			monacoSetup: this.monacoSetup,
 			monacoSetup: this.monacoSetup,
-			key: this.reloadKey,
+			reloadKey: this.reloadKey,
 		};
 		};
 	}
 	}
 
 
+	@observable.ref
+	private _previewState: IPreviewState | undefined = undefined;
+
+	public readonly getPreviewState = (): IPreviewState | undefined => {
+		return this._previewState;
+	};
+
+	public readonly getCompareWithPreviewState = ():
+		| IPreviewState
+		| undefined => {
+		const previewState = this.getPreviewState();
+		if (!previewState) {
+			return undefined;
+		}
+		return {
+			...previewState,
+			monacoSetup: toLoaderConfig({
+				...getDefaultSettings(),
+				...this.historyModel.compareWith!.toPartialSettings(),
+			}),
+		};
+	};
+
 	@observable
 	@observable
 	public settingsDialogModel: SettingsDialogModel | undefined = undefined;
 	public settingsDialogModel: SettingsDialogModel | undefined = undefined;
 
 
@@ -134,43 +148,50 @@ export class PlaygroundModel {
 						example: value,
 						example: value,
 						project: p,
 						project: p,
 					};
 					};
+					this.reloadKey++;
 					this.setState(p);
 					this.setState(p);
 				});
 				});
 			});
 			});
 		}
 		}
 	}
 	}
 
 
-	private readonly debouncer = new Debouncer(250);
+	private readonly debouncer = new Debouncer(700);
 
 
 	@observable
 	@observable
 	public isDirty = false;
 	public isDirty = false;
 
 
 	constructor() {
 	constructor() {
-		let lastState = this.state;
+		let lastState: IPreviewState | undefined = undefined;
 
 
 		this.dispose.track({
 		this.dispose.track({
 			dispose: reaction(
 			dispose: reaction(
 				() => ({ state: this.state }),
 				() => ({ state: this.state }),
-				({ state }) => {
+				() => {
+					const state = this.state;
 					if (!this.settings.autoReload) {
 					if (!this.settings.autoReload) {
 						if (
 						if (
-							JSON.stringify(state.monacoSetup) ===
-								JSON.stringify(lastState.monacoSetup) &&
-							state.key === lastState.key
+							(!lastState ||
+								JSON.stringify(state.monacoSetup) ===
+									JSON.stringify(lastState.monacoSetup)) &&
+							state.reloadKey === (lastState?.reloadKey ?? 0)
 						) {
 						) {
 							this.isDirty = true;
 							this.isDirty = true;
 							return;
 							return;
 						}
 						}
 					}
 					}
-					this.debouncer.run(() => {
+					const updatePreviewState = () => {
 						this.isDirty = false;
 						this.isDirty = false;
-						lastState = state;
-						for (const handler of this._previewHandlers) {
-							handler.handlePreview(state);
-						}
-					});
+						this._previewState = state;
+						lastState = this._previewState;
+					};
+
+					if (state.reloadKey !== lastState?.reloadKey) {
+						updatePreviewState();
+					} else {
+						this.debouncer.run(updatePreviewState);
+					}
 				},
 				},
-				{ name: "update preview" }
+				{ name: "update preview", fireImmediately: true }
 			),
 			),
 		});
 		});
 
 
@@ -178,6 +199,17 @@ export class PlaygroundModel {
 		let disposable: Disposable | undefined = undefined;
 		let disposable: Disposable | undefined = undefined;
 
 
 		waitForLoadedMonaco().then((m) => {
 		waitForLoadedMonaco().then((m) => {
+			this.dispose.track(
+				monaco.editor.addEditorAction({
+					id: "reload",
+					label: "Reload",
+					run: (editor, ...args) => {
+						this.reload();
+					},
+					keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
+				})
+			);
+
 			const options =
 			const options =
 				monaco.languages.typescript.javascriptDefaults.getCompilerOptions();
 				monaco.languages.typescript.javascriptDefaults.getCompilerOptions();
 			monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
 			monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
@@ -222,6 +254,28 @@ export class PlaygroundModel {
 		});
 		});
 	}
 	}
 
 
+	setCodeString(codeStringName: string, value: string) {
+		function escapeRegexpChars(str: string) {
+			return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
+		}
+
+		const regexp = new RegExp(
+			"(\\b" +
+				escapeRegexpChars(codeStringName) +
+				":[^\\w`]*`)([^`\\\\\\n]|\\n|\\\\\\\\|\\\\\\`|\\\\\\$)*`"
+		);
+		const js = this.js;
+		const str = value
+			.replaceAll("\\", "\\\\")
+			.replaceAll("$", "\\$$$$")
+			.replaceAll("`", "\\`");
+		const newJs = js.replace(regexp, "$1" + str + "`");
+		const autoReload = this.settings.autoReload;
+		this.settings.autoReload = false;
+		this.js = newJs;
+		this.settings.autoReload = autoReload;
+	}
+
 	public showSettingsDialog(): void {
 	public showSettingsDialog(): void {
 		this.settingsDialogModel = new SettingsDialogModel(
 		this.settingsDialogModel = new SettingsDialogModel(
 			this.settings.settings
 			this.settings.settings
@@ -245,21 +299,13 @@ export class PlaygroundModel {
 		this.css = state.css;
 		this.css = state.css;
 	}
 	}
 
 
-	public setPreviewHandler(handler: IPreviewHandler): monaco.IDisposable {
-		this._previewHandlers.add(handler);
-		handler.handlePreview(this.state);
-		return {
-			dispose: () => {
-				this._previewHandlers.delete(handler);
-			},
-		};
-	}
-
 	public readonly bisectModel = new BisectModel(this);
 	public readonly bisectModel = new BisectModel(this);
-}
 
 
-export interface IPreviewHandler {
-	handlePreview(state: IPreviewState): void;
+	@action
+	compareWithLatestDev(): void {
+		this.settings.previewFullScreen = true;
+		this.historyModel.compareWithLatestDev();
+	}
 }
 }
 
 
 export class SettingsDialogModel {
 export class SettingsDialogModel {
@@ -277,419 +323,3 @@ export class SettingsDialogModel {
 		this.settings = Object.assign({}, settings);
 		this.settings = Object.assign({}, settings);
 	}
 	}
 }
 }
-
-function projectEquals(
-	project1: IPlaygroundProject,
-	project2: IPlaygroundProject
-): boolean {
-	return (
-		project1.css === project2.css &&
-		project1.html === project2.html &&
-		project1.js === project2.js
-	);
-}
-
-class StateUrlSerializer implements IHistoryModel {
-	public readonly dispose = Disposable.fn();
-
-	private readonly compressor = new LzmaCompressor<IPlaygroundProject>();
-
-	private cachedState:
-		| { state: IPlaygroundProject; hash: string }
-		| undefined = undefined;
-
-	private readonly computedHashValue = debouncedComputed(
-		500,
-		() => ({
-			state: this.model.playgroundProject,
-			selectedExampleProject: this.model.selectedExampleProject,
-		}),
-		({ state, selectedExampleProject }) => {
-			if (
-				selectedExampleProject &&
-				projectEquals(state, selectedExampleProject.project)
-			) {
-				return "example-" + selectedExampleProject.example.id;
-			}
-			if (
-				this.cachedState &&
-				projectEquals(this.cachedState.state, state)
-			) {
-				return this.cachedState.hash;
-			}
-			return this.compressor.encodeData(state);
-		}
-	);
-
-	private get sourceFromSettings(): Source | undefined {
-		const settings = this.model.settings.settings;
-		if (settings.monacoSource === "npm") {
-			return new Source(settings.npmVersion, undefined, undefined);
-		} else if (
-			settings.monacoSource === "independent" &&
-			((settings.coreSource === "url" &&
-				(settings.languagesSource === "latest" ||
-					settings.languagesSource === "url")) ||
-				(settings.coreSource === "latest" &&
-					settings.languagesSource === "url"))
-		) {
-			return new Source(
-				undefined,
-				settings.coreSource === "url" ? settings.coreUrl : undefined,
-				settings.languagesSource === "latest"
-					? undefined
-					: settings.languagesUrl
-			);
-		} else if (settings.monacoSource === "latest") {
-			return new Source(monacoEditorVersion, undefined, undefined);
-		}
-		return undefined;
-	}
-
-	@observable
-	private _sourceOverride: Source | undefined;
-
-	get sourceOverride(): Source | undefined {
-		return this._sourceOverride;
-	}
-
-	@action
-	disableSourceOverride(): void {
-		this._sourceOverride = undefined;
-		this.historyId++;
-	}
-
-	@action
-	saveSourceOverride(): void {
-		if (this._sourceOverride) {
-			this.model.settings.setSettings({
-				...this.model.settings.settings,
-				...this._sourceOverride.toPartialSettings(),
-			});
-			this.historyId++;
-			this._sourceOverride = undefined;
-		}
-	}
-
-	/**
-	 * This is used to control replace/push state.
-	 * Replace is used if the history id does not change.
-	 */
-	@observable historyId: number = 0;
-
-	get location(): ILocation {
-		const source = this._sourceOverride || this.sourceFromSettings;
-		return {
-			hashValue: this.computedHashValue.value || this.cachedState?.hash,
-			searchParams: {
-				source: source?.sourceToString(),
-				sourceLanguages: source?.sourceLanguagesToString(),
-			},
-		};
-	}
-
-	@action
-	updateLocation(currentLocation: ILocation): void {
-		const hashValue = currentLocation.hashValue;
-		const sourceStr = currentLocation.searchParams.source;
-		const sourceLanguages = currentLocation.searchParams.sourceLanguages;
-		const source =
-			sourceStr || sourceLanguages
-				? Source.parse(sourceStr, sourceLanguages)
-				: undefined;
-
-		if (this.sourceFromSettings?.equals(source)) {
-			this._sourceOverride = undefined;
-		} else {
-			this._sourceOverride = source;
-		}
-
-		if (!hashValue) {
-			this.model.selectedExample = getPlaygroundExamples()[0].examples[0];
-		} else if (hashValue.startsWith("example-")) {
-			const exampleName = hashValue.substring("example-".length);
-			const example = getPlaygroundExamples()
-				.flatMap((e) => e.examples)
-				.find((e) => e.id === exampleName);
-			if (example) {
-				this.model.selectedExample = example;
-			}
-		} else {
-			let p: IPlaygroundProject | undefined = undefined;
-			if (this.cachedState?.hash === hashValue) {
-				p = this.cachedState.state;
-			}
-			if (!p) {
-				try {
-					p =
-						this.compressor.decodeData<IPlaygroundProject>(
-							hashValue
-						);
-				} catch (e) {
-					console.log("Could not deserialize from hash value", e);
-				}
-			}
-			if (p) {
-				this.cachedState = { state: p, hash: hashValue };
-				this.model.setState(p);
-			}
-		}
-	}
-
-	private readonly historyController = this.dispose.track(
-		new HistoryController((initialLocation) => {
-			this.updateLocation(initialLocation);
-			return this;
-		})
-	);
-
-	constructor(private readonly model: PlaygroundModel) {}
-}
-
-class BisectModel {
-	private readonly map = new ObservableMap<string, boolean>();
-
-	constructor(private readonly model: PlaygroundModel) {}
-
-	public getState(version: string): boolean | undefined {
-		return this.map.get(version);
-	}
-
-	public get isActive() {
-		return [...this.map.values()].some((e) => e !== undefined);
-	}
-
-	public reset(): void {
-		this.map.clear();
-	}
-
-	public async toggleState(version: string, state: boolean): Promise<void> {
-		const currentState = this.getState(version);
-		await this.setState(
-			version,
-			currentState === state ? undefined : state
-		);
-	}
-
-	@action
-	public async setState(
-		version: string,
-		state: boolean | undefined
-	): Promise<void> {
-		if (state === undefined) {
-			this.map.delete(version);
-		} else {
-			this.map.set(version, state);
-		}
-
-		const nextVersion = await this.getNextVersion();
-		if (!nextVersion) {
-			return;
-		}
-		this.model.settings.setSettings({
-			...this.model.settings.settings,
-			npmVersion: nextVersion,
-		});
-	}
-
-	private get versions() {
-		return getNpmVersionsSync(undefined);
-	}
-
-	private get indexOfLastBadVersion() {
-		return findLastIndex(this.versions, (v) => this.map.get(v) === false);
-	}
-	private get indexOfFirstGoodVersion() {
-		return this.versions.findIndex((v) => this.map.get(v) === true);
-	}
-
-	public get steps() {
-		const indexOfFirstGoodVersion = this.indexOfFirstGoodVersion;
-		const indexOfLastBadVersion = this.indexOfLastBadVersion;
-
-		if (indexOfFirstGoodVersion === -1 && indexOfLastBadVersion === -1) {
-			return -1;
-		}
-		if (indexOfFirstGoodVersion === -1) {
-			return Math.ceil(
-				Math.log2(this.versions.length - indexOfLastBadVersion)
-			);
-		} else if (indexOfLastBadVersion === -1) {
-			return Math.ceil(Math.log2(indexOfFirstGoodVersion + 1));
-		} else {
-			return Math.ceil(
-				Math.log2(indexOfFirstGoodVersion - indexOfLastBadVersion)
-			);
-		}
-	}
-
-	public get isFinished() {
-		if (
-			this.indexOfFirstGoodVersion !== -1 &&
-			this.indexOfLastBadVersion + 1 === this.indexOfFirstGoodVersion
-		) {
-			return true;
-		}
-		return false;
-	}
-
-	public async openGithub() {
-		const versions = await getNpmVersions();
-		const indexOfFirstGoodVersion =
-			this.indexOfFirstGoodVersion === -1
-				? versions.length - 1
-				: this.indexOfFirstGoodVersion;
-		const indexOfLastBadVersion =
-			this.indexOfLastBadVersion === -1 ? 0 : this.indexOfLastBadVersion;
-		const goodCommitId = await getVsCodeCommitId(
-			versions[indexOfFirstGoodVersion]
-		);
-		const badCommitId = await getVsCodeCommitId(
-			versions[indexOfLastBadVersion]
-		);
-		window.open(
-			`https://github.com/microsoft/vscode/compare/${goodCommitId}...${badCommitId}`,
-			"_blank"
-		);
-	}
-
-	private async getNextVersion(): Promise<string | undefined> {
-		const versions = await getNpmVersions();
-
-		const indexOfFirstGoodVersion = this.indexOfFirstGoodVersion;
-		const indexOfLastBadVersion = this.indexOfLastBadVersion;
-
-		if (
-			indexOfFirstGoodVersion !== -1 &&
-			indexOfLastBadVersion + 1 === indexOfFirstGoodVersion
-		) {
-			// Finished
-			return;
-		}
-
-		if (indexOfLastBadVersion === -1 && indexOfFirstGoodVersion === -1) {
-			return versions[0];
-		}
-		if (indexOfLastBadVersion === -1) {
-			// try first (newest) version that hasn't been tested
-			const indexOfFirstUntestedVersion = versions.findIndex(
-				(v) => this.map.get(v) === undefined
-			);
-			if (indexOfFirstUntestedVersion === -1) {
-				return undefined;
-			}
-			return versions[indexOfFirstUntestedVersion];
-		}
-
-		if (indexOfFirstGoodVersion === -1) {
-			/*// exponential back off, might be good for recent regressions, but ruins step counter
-			const candidate = Math.min(
-				indexOfLastBadVersion * 2 + 1,
-				versions.length - 1
-			);*/
-			const candidate = Math.floor(
-				(indexOfLastBadVersion + versions.length) / 2
-			);
-			return versions[candidate];
-		}
-
-		return versions[
-			Math.floor((indexOfLastBadVersion + indexOfFirstGoodVersion) / 2)
-		];
-	}
-}
-
-function findLastIndex<T>(
-	array: T[],
-	predicate: (value: T) => boolean
-): number {
-	for (let i = array.length - 1; i >= 0; i--) {
-		if (predicate(array[i])) {
-			return i;
-		}
-	}
-	return -1;
-}
-
-class Source {
-	public static parse(
-		sourceStr: string | undefined,
-		sourceLanguagesStr: string | undefined
-	): Source {
-		if (sourceStr && sourceStr.startsWith("v")) {
-			return new Source(
-				sourceStr.substring(1),
-				undefined,
-				sourceLanguagesStr
-			);
-		}
-		return new Source(undefined, sourceStr, sourceLanguagesStr);
-	}
-
-	public equals(other: Source | undefined): boolean {
-		if (!other) {
-			return false;
-		}
-		return other.toString() === this.toString();
-	}
-
-	constructor(
-		public readonly version: string | undefined,
-		public readonly url: string | undefined,
-		public readonly sourceLanguagesStr: string | undefined
-	) {
-		if (
-			version === undefined &&
-			url === undefined &&
-			sourceLanguagesStr === undefined
-		) {
-			throw new Error("one parameter must be defined");
-		}
-	}
-
-	sourceToString(): string | undefined {
-		if (this.url) {
-			return this.url;
-		}
-		if (this.version) {
-			return `v${this.version}`;
-		}
-		return undefined;
-	}
-
-	sourceLanguagesToString(): string | undefined {
-		return this.sourceLanguagesStr;
-	}
-
-	toString() {
-		return `${this.sourceToString()};${this.sourceLanguagesToString()}`;
-	}
-
-	public toPartialSettings(): Partial<Settings> {
-		const languagesSettings: Partial<Settings> = {
-			languagesSource:
-				this.sourceLanguagesStr === undefined ? "latest" : "url",
-			languagesUrl: this.sourceLanguagesStr,
-		};
-
-		if (this.version) {
-			return {
-				monacoSource: "npm",
-				npmVersion: this.version,
-			};
-		} else if (this.url) {
-			return {
-				monacoSource: "independent",
-				coreSource: "url",
-				coreUrl: this.url,
-				...languagesSettings,
-			};
-		} else {
-			return {
-				monacoSource: "independent",
-				coreSource: "latest",
-				...languagesSettings,
-			};
-		}
-	}
-}

+ 12 - 0
website/src/website/pages/playground/PlaygroundPage.tsx

@@ -3,7 +3,19 @@ import * as React from "react";
 import { hotComponent } from "../../utils/hotComponent";
 import { hotComponent } from "../../utils/hotComponent";
 import { PlaygroundModel } from "./PlaygroundModel";
 import { PlaygroundModel } from "./PlaygroundModel";
 import { PlaygroundPageContent } from "./PlaygroundPageContent";
 import { PlaygroundPageContent } from "./PlaygroundPageContent";
+import { withLoader } from "../../components/Loader";
+import { getNpmVersions } from "./getNpmVersionsSync";
 
 
+@withLoader(async () => {
+	const search = new URLSearchParams(window.location.search);
+	if (
+		search.get("source") === "latest-dev" ||
+		search.get("compareWith") === "latest-dev"
+	) {
+		// So that the source class can resolve that value
+		await getNpmVersions();
+	}
+})
 @hotComponent(module)
 @hotComponent(module)
 @observer
 @observer
 export class PlaygroundPage extends React.Component {
 export class PlaygroundPage extends React.Component {

+ 145 - 56
website/src/website/pages/playground/PlaygroundPageContent.tsx

@@ -1,24 +1,25 @@
 import { autorun } from "mobx";
 import { autorun } from "mobx";
 import { observer } from "mobx-react";
 import { observer } from "mobx-react";
 import * as React from "react";
 import * as React from "react";
+import { ButtonGroup, FormCheck } from "react-bootstrap";
 import { getLoadedMonaco } from "../../../monaco-loader";
 import { getLoadedMonaco } from "../../../monaco-loader";
-import { IPlaygroundProject, IPreviewState } from "../../../shared";
 import { Page } from "../../components/Page";
 import { Page } from "../../components/Page";
 import { Select } from "../../components/Select";
 import { Select } from "../../components/Select";
+import { Button, Col, Row, Stack } from "../../components/bootstrap";
 import {
 import {
 	MonacoEditor,
 	MonacoEditor,
 	MonacoEditorHeight,
 	MonacoEditorHeight,
 } from "../../components/monaco/MonacoEditor";
 } from "../../components/monaco/MonacoEditor";
 import { withLoadedMonaco } from "../../components/monaco/MonacoLoader";
 import { withLoadedMonaco } from "../../components/monaco/MonacoLoader";
+import { monacoEditorVersion } from "../../monacoEditorVersion";
 import { hotComponent } from "../../utils/hotComponent";
 import { hotComponent } from "../../utils/hotComponent";
 import { IReference, ref } from "../../utils/ref";
 import { IReference, ref } from "../../utils/ref";
-import { getNpmVersionsSync } from "./getNpmVersionsSync";
-import { getPlaygroundExamples, PlaygroundExample } from "./playgroundExamples";
 import { PlaygroundModel } from "./PlaygroundModel";
 import { PlaygroundModel } from "./PlaygroundModel";
 import { Preview } from "./Preview";
 import { Preview } from "./Preview";
 import { SettingsDialog } from "./SettingsDialog";
 import { SettingsDialog } from "./SettingsDialog";
-import { Button, Col, Row, Stack } from "../../components/bootstrap";
-import { ButtonGroup, FormCheck } from "react-bootstrap";
+import { getNpmVersionsSync } from "./getNpmVersionsSync";
+import { PlaygroundExample, getPlaygroundExamples } from "./playgroundExamples";
+import { getDefaultSettings, toLoaderConfig } from "./SettingsModel";
 
 
 @hotComponent(module)
 @hotComponent(module)
 @observer
 @observer
@@ -41,7 +42,7 @@ export class PlaygroundPageContent extends React.Component<
 							<Col
 							<Col
 								md
 								md
 								className={
 								className={
-									model.settings.previewFullScreen
+									model.previewShouldBeFullScreen
 										? "d-none"
 										? "d-none"
 										: ""
 										: ""
 								}
 								}
@@ -118,25 +119,40 @@ export class PlaygroundPageContent extends React.Component<
 								</Vertical>
 								</Vertical>
 							</Col>
 							</Col>
 						)}
 						)}
-						<Col md>
+						<Col
+							md
+							style={{ display: "flex", flexDirection: "column" }}
+						>
 							<LabeledEditor
 							<LabeledEditor
-								label="Preview"
+								label={`Preview${
+									model.historyModel.compareWith &&
+									model.historyModel.sourceOverride
+										? " " +
+										  model.historyModel.sourceOverride.toString()
+										: ""
+								}:`}
 								titleBarItems={
 								titleBarItems={
 									<div
 									<div
 										style={{ marginLeft: "auto" }}
 										style={{ marginLeft: "auto" }}
 										className="d-flex gap-2 align-items-center"
 										className="d-flex gap-2 align-items-center"
 									>
 									>
-										{model.settings.previewFullScreen || (
+										{model.previewShouldBeFullScreen || (
 											<FormCheck
 											<FormCheck
 												label="Auto-Reload"
 												label="Auto-Reload"
 												className="text-nowrap"
 												className="text-nowrap"
 												checked={
 												checked={
 													model.settings.autoReload
 													model.settings.autoReload
 												}
 												}
-												onChange={(e) =>
-													(model.settings.autoReload =
-														e.target.checked)
-												}
+												onChange={(e) => {
+													model.settings.autoReload =
+														e.target.checked;
+													if (
+														e.target.checked &&
+														model.isDirty
+													) {
+														model.reload();
+													}
+												}}
 											/>
 											/>
 										)}
 										)}
 										<Button
 										<Button
@@ -171,55 +187,116 @@ export class PlaygroundPageContent extends React.Component<
 											}
 											}
 										/>
 										/>
 
 
-										{model.serializer.sourceOverride ? (
+										{!model.historyModel.compareWith ? (
+											model.historyModel
+												.sourceOverride ? (
+												<ButtonGroup>
+													<button
+														type="button"
+														className="btn btn-primary"
+														onClick={() =>
+															model.historyModel.disableSourceOverride()
+														}
+													>
+														Disable{" "}
+														{model.historyModel
+															.sourceOverride
+															.version ??
+															"url"}{" "}
+														override
+													</button>
+													<button
+														type="button"
+														className="btn btn-secondary"
+														onClick={() =>
+															model.compareWithLatestDev()
+														}
+													>
+														Compare with latest dev
+													</button>
+													<button
+														type="button"
+														className="btn btn-secondary"
+														onClick={() =>
+															model.historyModel.saveSourceOverride()
+														}
+													>
+														Save
+													</button>
+												</ButtonGroup>
+											) : (
+												<>
+													<VersionSelector
+														model={model}
+													/>
+
+													<button
+														type="button"
+														className="btn btn-light settings bi-gear"
+														style={{
+															fontSize: 20,
+															padding: "0px 4px",
+														}}
+														onClick={() =>
+															model.showSettingsDialog()
+														}
+													/>
+												</>
+											)
+										) : (
 											<ButtonGroup>
 											<ButtonGroup>
 												<button
 												<button
 													type="button"
 													type="button"
 													className="btn btn-primary"
 													className="btn btn-primary"
 													onClick={() =>
 													onClick={() =>
-														model.serializer.disableSourceOverride()
+														model.historyModel.exitCompare()
 													}
 													}
 												>
 												>
-													Disable{" "}
-													{model.serializer
-														.sourceOverride
-														.version ?? "url"}{" "}
-													override
-												</button>
-												<button
-													type="button"
-													className="btn btn-secondary"
-													onClick={() =>
-														model.serializer.saveSourceOverride()
-													}
-												>
-													Save
+													Exit Compare
 												</button>
 												</button>
 											</ButtonGroup>
 											</ButtonGroup>
-										) : (
-											<>
-												<VersionSelector
-													model={model}
-												/>
-
-												<button
-													type="button"
-													className="btn btn-light settings bi-gear"
-													style={{
-														fontSize: 20,
-														padding: "0px 4px",
-													}}
-													onClick={() =>
-														model.showSettingsDialog()
-													}
-												/>
-											</>
 										)}
 										)}
 									</div>
 									</div>
 								}
 								}
 							>
 							>
-								<Preview model={model} />
+								<Preview
+									model={model}
+									getPreviewState={model.getPreviewState}
+								/>
 							</LabeledEditor>
 							</LabeledEditor>
+							{model.historyModel.compareWith && (
+								<>
+									<div style={{ height: "10px" }} />
+									<LabeledEditor
+										label={`Preview ${model.historyModel.compareWith.toString()}:`}
+										titleBarItems={
+											<div
+												style={{ marginLeft: "auto" }}
+												className="d-flex gap-2 align-items-center"
+											>
+												<ButtonGroup>
+													<button
+														type="button"
+														className="btn btn-primary"
+														onClick={() =>
+															model.historyModel.saveCompareWith()
+														}
+													>
+														Save
+													</button>
+												</ButtonGroup>
+											</div>
+										}
+									>
+										<Preview
+											model={model}
+											getPreviewState={
+												model.getCompareWithPreviewState
+											}
+										/>
+									</LabeledEditor>
+								</>
+							)}
 						</Col>
 						</Col>
 					</Row>
 					</Row>
 				</div>
 				</div>
@@ -252,13 +329,15 @@ export class VersionSelector extends React.Component<{
 				<Select
 				<Select
 					values={versions}
 					values={versions}
 					getLabel={(i) =>
 					getLabel={(i) =>
-						`${i}${
-							{
-								["undefined"]: "",
-								["true"]: " ✓",
-								["false"]: " ✗",
-							}["" + model.bisectModel.getState(i)]
-						}`
+						i === latestValue
+							? `latest stable (${monacoEditorVersion})`
+							: `${i}${
+									{
+										["undefined"]: "",
+										["true"]: " ✓",
+										["false"]: " ✗",
+									}["" + model.bisectModel.getState(i)]
+							  }`
 					}
 					}
 					value={{
 					value={{
 						get() {
 						get() {
@@ -442,7 +521,16 @@ class Editor extends React.Component<{
 				() => {
 				() => {
 					const value = this.props.value.get();
 					const value = this.props.value.get();
 					if (!this.ignoreChange) {
 					if (!this.ignoreChange) {
-						this.editor!.setValue(value);
+						this.model.pushEditOperations(
+							null,
+							[
+								{
+									range: this.model.getFullModelRange(),
+									text: value,
+								},
+							],
+							() => null
+						);
 					}
 					}
 				},
 				},
 				{ name: "update text" }
 				{ name: "update text" }
@@ -452,6 +540,7 @@ class Editor extends React.Component<{
 
 
 	componentWillUnmount() {
 	componentWillUnmount() {
 		this.disposables.forEach((d) => d.dispose());
 		this.disposables.forEach((d) => d.dispose());
+		this.model.dispose();
 	}
 	}
 }
 }
 
 

+ 67 - 20
website/src/website/pages/playground/Preview.tsx

@@ -1,23 +1,53 @@
 import * as React from "react";
 import * as React from "react";
-import { IPreviewHandler, PlaygroundModel } from "./PlaygroundModel";
+import { PlaygroundModel } from "./PlaygroundModel";
 import { observer } from "mobx-react";
 import { observer } from "mobx-react";
-import { observable } from "mobx";
-import { IMessage, IPreviewState } from "../../../shared";
+import { autorun, observable, reaction } from "mobx";
+import {
+	IMessageFromRunner,
+	IMessageToRunner,
+	IPreviewState,
+} from "../../../shared";
+import { Button } from "react-bootstrap";
 
 
 @observer
 @observer
-export class Preview
-	extends React.Component<{ model: PlaygroundModel }>
-	implements IPreviewHandler
-{
+export class Preview extends React.Component<{
+	model: PlaygroundModel;
+	getPreviewState: () => IPreviewState | undefined;
+}> {
 	private disposables: monaco.IDisposable[] = [];
 	private disposables: monaco.IDisposable[] = [];
-	@observable
-	private counter = 0;
-	private currentState: IPreviewState | undefined;
+	@observable private counter = 0;
+	@observable.ref private currentState: IPreviewState | undefined;
 	private iframe: HTMLIFrameElement | null = null;
 	private iframe: HTMLIFrameElement | null = null;
 
 
 	render() {
 	render() {
 		return (
 		return (
 			<div className="preview">
 			<div className="preview">
+				{this.currentState ? null : (
+					<div
+						style={{
+							width: "100%",
+							height: "100%",
+							display: "flex",
+							justifyContent: "center",
+							alignItems: "center",
+						}}
+					>
+						<div>
+							Load{" "}
+							<Button
+								type="button"
+								className={
+									"btn settings bi-arrow-clockwise btn-primary"
+								}
+								style={{
+									fontSize: 20,
+									padding: "0px 4px",
+								}}
+								onClick={() => this.props.model.reload()}
+							/>
+						</div>
+					</div>
+				)}
 				<iframe
 				<iframe
 					className="full-iframe"
 					className="full-iframe"
 					key={this.counter}
 					key={this.counter}
@@ -40,7 +70,7 @@ export class Preview
 				return;
 				return;
 			}
 			}
 
 
-			const message: IMessage = {
+			const message: IMessageToRunner = {
 				kind: "initialize",
 				kind: "initialize",
 				state: this.currentState,
 				state: this.currentState,
 			};
 			};
@@ -48,30 +78,47 @@ export class Preview
 				targetOrigin: "*",
 				targetOrigin: "*",
 			});
 			});
 		});
 		});
+		window.addEventListener("message", (e) => {
+			if (e.source !== iframe.contentWindow) {
+				return;
+			}
+			const data = e.data as IMessageFromRunner;
+			if (data.kind === "update-code-string") {
+				this.props.model.setCodeString(data.codeStringName, data.value);
+			} else if (data.kind === "reload") {
+				this.props.model.reload();
+			}
+		});
 	};
 	};
 
 
 	componentDidMount() {
 	componentDidMount() {
-		this.disposables.push(this.props.model.setPreviewHandler(this));
+		this.disposables.push({
+			dispose: reaction(
+				() => this.props.getPreviewState(),
+				(state) => {
+					if (state) {
+						console.log("handlePreview", state);
+						this.handlePreview(state);
+					}
+				},
+				{ fireImmediately: true }
+			),
+		});
 	}
 	}
 
 
 	componentWillUnmount() {
 	componentWillUnmount() {
 		this.disposables.forEach((d) => d.dispose());
 		this.disposables.forEach((d) => d.dispose());
 	}
 	}
 
 
-	handlePreview(state: IPreviewState): void {
+	private handlePreview(state: IPreviewState): void {
 		if (
 		if (
 			JSON.stringify({ ...state, css: "" }) ===
 			JSON.stringify({ ...state, css: "" }) ===
 			JSON.stringify({ ...this.currentState, css: "" })
 			JSON.stringify({ ...this.currentState, css: "" })
 		) {
 		) {
 			// only css changed
 			// only css changed
 			this.iframe?.contentWindow!.postMessage(
 			this.iframe?.contentWindow!.postMessage(
-				{
-					kind: "update-css",
-					css: state.css,
-				} as IMessage,
-				{
-					targetOrigin: "*",
-				}
+				{ kind: "update-css", css: state.css } as IMessageToRunner,
+				{ targetOrigin: "*" }
 			);
 			);
 			this.currentState = state;
 			this.currentState = state;
 		} else {
 		} else {

+ 107 - 0
website/src/website/pages/playground/Source.ts

@@ -0,0 +1,107 @@
+import { monacoEditorVersion } from "../../monacoEditorVersion";
+import { getNpmVersionsSync } from "./getNpmVersionsSync";
+import { Settings } from "./SettingsModel";
+
+export class Source {
+	public static useLatestDev(sourceLanguagesStr?: string): Source {
+		// Assume the versions are already loaded
+		const versions = getNpmVersionsSync(undefined);
+		const version = versions.find((v) => v.indexOf("-dev-") !== -1);
+		return new Source(version, undefined, sourceLanguagesStr);
+	}
+
+	public static useLatest(sourceLanguagesStr?: string): Source {
+		return new Source(monacoEditorVersion, undefined, sourceLanguagesStr);
+	}
+
+	public static parse(
+		sourceStr: string | undefined,
+		sourceLanguagesStr: string | undefined
+	): Source {
+		if (sourceStr === "latest-dev") {
+			return Source.useLatestDev(sourceLanguagesStr);
+		}
+		if (sourceStr === "latest") {
+			return Source.useLatest(sourceLanguagesStr);
+		}
+
+		if (sourceStr && sourceStr.startsWith("v")) {
+			return new Source(
+				sourceStr.substring(1),
+				undefined,
+				sourceLanguagesStr
+			);
+		}
+		return new Source(undefined, sourceStr, sourceLanguagesStr);
+	}
+
+	public equals(other: Source | undefined): boolean {
+		if (!other) {
+			return false;
+		}
+		return other.toString() === this.toString();
+	}
+
+	constructor(
+		public readonly version: string | undefined,
+		public readonly url: string | undefined,
+		public readonly sourceLanguagesStr: string | undefined
+	) {
+		if (
+			version === undefined &&
+			url === undefined &&
+			sourceLanguagesStr === undefined
+		) {
+			throw new Error("one parameter must be defined");
+		}
+	}
+
+	sourceToString(): string | undefined {
+		if (this.url) {
+			return this.url;
+		}
+		if (this.version) {
+			return `v${this.version}`;
+		}
+		return undefined;
+	}
+
+	sourceLanguagesToString(): string | undefined {
+		return this.sourceLanguagesStr;
+	}
+
+	toString() {
+		const sourceLangToStr = this.sourceLanguagesToString();
+		return `${this.sourceToString()}${
+			sourceLangToStr ? `;${sourceLangToStr}` : ""
+		}`;
+	}
+
+	public toPartialSettings(): Partial<Settings> {
+		const languagesSettings: Partial<Settings> = {
+			languagesSource:
+				this.sourceLanguagesStr === undefined ? "latest" : "url",
+			languagesUrl: this.sourceLanguagesStr,
+		};
+
+		if (this.version) {
+			return {
+				monacoSource: "npm",
+				npmVersion: this.version,
+			};
+		} else if (this.url) {
+			return {
+				monacoSource: "independent",
+				coreSource: "url",
+				coreUrl: this.url,
+				...languagesSettings,
+			};
+		} else {
+			return {
+				monacoSource: "independent",
+				coreSource: "latest",
+				...languagesSettings,
+			};
+		}
+	}
+}

+ 8 - 1
website/src/website/pages/playground/getNpmVersionsSync.ts

@@ -6,7 +6,7 @@ export function getNpmVersionsSync(
 	currentVersion: string | undefined
 	currentVersion: string | undefined
 ): string[] {
 ): string[] {
 	if (!npmVersionsObservable) {
 	if (!npmVersionsObservable) {
-		npmVersionsObservable = new ObservablePromise(getNpmVersions());
+		npmVersionsObservable = new ObservablePromise(loadNpmVersions());
 	}
 	}
 	return (
 	return (
 		npmVersionsObservable.value || (currentVersion ? [currentVersion] : [])
 		npmVersionsObservable.value || (currentVersion ? [currentVersion] : [])
@@ -16,6 +16,13 @@ export function getNpmVersionsSync(
 let npmVersionsPromise: Promise<string[]> | undefined;
 let npmVersionsPromise: Promise<string[]> | undefined;
 
 
 export async function getNpmVersions(): Promise<string[]> {
 export async function getNpmVersions(): Promise<string[]> {
+	getNpmVersionsSync(undefined);
+	return npmVersionsPromise!;
+}
+
+getNpmVersions();
+
+async function loadNpmVersions(): Promise<string[]> {
 	if (npmVersionsPromise === undefined) {
 	if (npmVersionsPromise === undefined) {
 		npmVersionsPromise = _getNpmVersions();
 		npmVersionsPromise = _getNpmVersions();
 	}
 	}

+ 29 - 0
website/src/website/pages/playground/utils.ts

@@ -0,0 +1,29 @@
+import { normalizeLineEnding } from "./utils";
+import { IPlaygroundProject } from "../../../shared";
+
+export function findLastIndex<T>(
+	array: T[],
+	predicate: (value: T) => boolean
+): number {
+	for (let i = array.length - 1; i >= 0; i--) {
+		if (predicate(array[i])) {
+			return i;
+		}
+	}
+	return -1;
+}
+export function projectEquals(
+	project1: IPlaygroundProject,
+	project2: IPlaygroundProject
+): boolean {
+	return (
+		normalizeLineEnding(project1.css) ===
+			normalizeLineEnding(project2.css) &&
+		normalizeLineEnding(project1.html) ===
+			normalizeLineEnding(project2.html) &&
+		normalizeLineEnding(project1.js) === normalizeLineEnding(project2.js)
+	);
+}
+export function normalizeLineEnding(str: string): string {
+	return str.replace(/\r\n/g, "\n");
+}

+ 3 - 0
website/src/website/style.scss

@@ -74,6 +74,9 @@ body,
 
 
 .monaco-editor {
 .monaco-editor {
 	position: absolute !important;
 	position: absolute !important;
+	a {
+		text-decoration: none;
+	}
 }
 }
 
 
 button.settings {
 button.settings {

+ 1 - 1
website/static/monarch/monarch.js

@@ -58,7 +58,7 @@ function createLangModel(languageId, text) {
 	var update = function () {
 	var update = function () {
 		var def = null;
 		var def = null;
 		try {
 		try {
-			def = eval("(function(){ " + langModel.getValue() + "; })()");
+			def = eval("(function(){ " + langModel.getValue() + "; })()"); // CodeQL [SM01632] langModel.getValue() is a default value with volatile user modifications. This is an essential functionality for the monarch playground and safe, as no injection is possible.
 		} catch (err) {
 		} catch (err) {
 			setInnerText(outputPane, err + "\n");
 			setInnerText(outputPane, err + "\n");
 			return;
 			return;

+ 10 - 10
website/yarn.lock

@@ -2147,10 +2147,10 @@ mobx@^5.15.4:
   resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.15.7.tgz#b9a5f2b6251f5d96980d13c78e9b5d8d4ce22665"
   resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.15.7.tgz#b9a5f2b6251f5d96980d13c78e9b5d8d4ce22665"
   integrity sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==
   integrity sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==
 
 
-monaco-editor@^0.35.0:
-  version "0.35.0"
-  resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.35.0.tgz#49c4220c815262a900dacf0ae8a59bef66efab8b"
-  integrity sha512-BJfkAZ0EJ7JgrgWzqjfBNP9hPSS8NlfECEDMEIIiozV2UaPq22yeuOjgbd3TwMh3anH0krWZirXZfn8KUSxiOA==
+monaco-editor@^0.42.0-dev-20230906:
+  version "0.42.0-dev-20230906"
+  resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.42.0-dev-20230906.tgz#612a41fcbed662d3873a94ad5f558e6893da2c7d"
+  integrity sha512-UICbxxHu0jYovjOKcwSJkmnJbokiSefro1wDqVJ4OpyzXmS0dYZol+lYPJLIdfb0oUtUTf8840VMAPo5jC+B1Q==
 
 
 mrmime@^1.0.0:
 mrmime@^1.0.0:
   version "1.0.1"
   version "1.0.1"
@@ -2828,14 +2828,14 @@ selfsigned@^2.0.1:
     node-forge "^1"
     node-forge "^1"
 
 
 semver@^6.3.0:
 semver@^6.3.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
-  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+  version "6.3.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
+  integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
 
 
 semver@^7.3.4:
 semver@^7.3.4:
-  version "7.3.7"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
-  integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
+  version "7.5.4"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
+  integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
   dependencies:
   dependencies:
     lru-cache "^6.0.0"
     lru-cache "^6.0.0"
 
 

部分文件因文件數量過多而無法顯示