Merge branch 'main' into feature/json_docsym

pull/3894/head
tamayika 1 year ago committed by GitHub
commit c38f07a36e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -23,6 +23,16 @@ resources:
ref: main
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:
template: azure-pipelines/npm-package/pipeline.yml@templates
parameters:
@ -31,10 +41,17 @@ extends:
workingDirectory: $(Build.SourcesDirectory)/dependencies/vscode/out-monaco-editor-core
testPlatforms: []
buildSteps:
- script: sudo apt install -y libkrb5-dev
displayName: Install libkrb5-dev
- script: npm ci
displayName: Install NPM dependencies
- 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
tag: next
@ -50,6 +67,10 @@ extends:
displayName: Install NPM dependencies
- 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
tag: next

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

@ -18,7 +18,7 @@ jobs:
uses: actions/cache@v2
with:
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-
- name: execute `npm ci` (1)

@ -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;
}
}

@ -1,23 +1,34 @@
name: Publish Website
on:
push:
tags:
- 'v*'
# enable users to manually trigger with workflow_dispatch
schedule:
- cron: 0 23 * * *
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:
publish-website:
name: Publish Website
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@dc323e67f16fb5f7663d20ff7941f27f5809e9b6 # pin@v2
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # pin@v2
with:
node-version: 16
- name: Cache node modules
id: cacheNodeModules
uses: actions/cache@v2
@ -25,11 +36,9 @@ jobs:
path: '**/node_modules'
key: ${{ runner.os }}-cacheNodeModules2-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-cacheNodeModules2-
- name: execute `npm ci` (1)
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
run: npm ci
- name: Build
run: npm run build-monaco-editor
@ -45,8 +54,13 @@ jobs:
working-directory: website
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:
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

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

@ -1,5 +1,65 @@
# 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]
- Fixes Inline Completions feature

@ -2,29 +2,26 @@
(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**
- https://github.com/microsoft/monaco-editor/tree/main/webpack-plugin
@ -41,3 +38,10 @@
- use `npm version major`
- publish using `npm publish`
- 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

@ -31,13 +31,39 @@ You will get:
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
- Learn how to integrate the editor with these [complete samples](./samples/).
- [Integrate the AMD version](./docs/integrate-amd.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).
- 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!
- 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!

@ -37,6 +37,16 @@ export const typescriptVersion = "${typeScriptDependencyVersion}";\n`
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
// 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.

@ -10,8 +10,6 @@ import yaserver = require('yaserver');
import { REPO_ROOT } from './utils';
import { ensureDir } from './fs';
const WEBSITE_GENERATED_PATH = path.join(REPO_ROOT, 'website/playground/new-samples');
generateTestSamplesTask();
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');
ensureDir(path.dirname(destination));
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) {

782
package-lock.json generated

File diff suppressed because it is too large Load Diff

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

@ -13,7 +13,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"typescript": "^5.0.2",
"vite": "^2.9.13"
"vite": "^2.9.16"
}
},
"node_modules/@ampproject/remapping": {
@ -1372,15 +1372,15 @@
}
},
"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,
"dependencies": {
"esbuild": "^0.14.27",
"postcss": "^8.4.13",
"resolve": "^1.22.0",
"rollup": "^2.59.0"
"rollup": ">=2.59.0 <2.78.0"
},
"bin": {
"vite": "bin/vite.js"
@ -2298,16 +2298,16 @@
"dev": true
},
"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,
"requires": {
"esbuild": "^0.14.27",
"fsevents": "~2.3.2",
"postcss": "^8.4.13",
"resolve": "^1.22.0",
"rollup": "^2.59.0"
"rollup": ">=2.59.0 <2.78.0"
}
}
}

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

@ -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;
}

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

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

@ -73,14 +73,17 @@ export async function writeJsonFile(filePath: string, jsonData: unknown): Promis
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 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}`;
prerelease = prerelease.replace('${today}', `${yyyy}${mm}${dd}`);
return `0.${minor + 1}.0-${prerelease}`;
}
export interface PackageJson {

@ -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)
[
{
@ -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)
[
{
@ -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
[
{

@ -333,7 +333,8 @@ export const language = <languages.IMonarchLanguage>{
// See https://elixir-lang.org/getting-started/sigils.html
// 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.
// Optional modifiers are allowed after the closing delimiter.
// For instance a regular expressions can be written as:
@ -353,16 +354,16 @@ export const language = <languages.IMonarchLanguage>{
sigils: [
[/~[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: [
[/~([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' }
]
],
@ -475,7 +476,7 @@ export const language = <languages.IMonarchLanguage>{
// Fallback to the generic sigil by default
'sigilStart.interpol': [
[
/~([a-zA-Z])@sigilStartDelimiter/,
/~([a-z]|[A-Z]+)@sigilStartDelimiter/,
{
token: 'sigil.delimiter',
switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'
@ -498,7 +499,7 @@ export const language = <languages.IMonarchLanguage>{
'sigilStart.noInterpol': [
[
/~([a-zA-Z])@sigilStartDelimiter/,
/~([a-z]|[A-Z]+)@sigilStartDelimiter/,
{
token: 'sigil.delimiter',
switchTo: '@sigilContinue.$S2.$S3.$S4.$S5'

@ -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
[
{

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

@ -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');
}
}
});

@ -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' }
]
}
]
]
);

@ -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']
]
}
};

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

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

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

@ -734,6 +734,15 @@ testTokenization(
tokens: [{ startIndex: 0, type: 'comment.twig' }]
}
],
[
{
line: 'test {# Hello World! #}',
tokens: [
{ startIndex: 0, type: '' },
{ startIndex: 5, type: 'comment.twig' }
]
}
],
[
{
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) }}',
@ -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 %}',

@ -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' }]],
[/</, 'delimiter.html'],
[/[^<]+/] // text
[/[^<{]+/] // text
],
/**

@ -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
[
{

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

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

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

@ -40937,7 +40937,7 @@ ${lanes.join("\n")}
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
}
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));
if (state.traceEnabled) {
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) {
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);
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))) {

@ -3,15 +3,17 @@
* 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 { libFileMap } from './lib/lib';
self.onmessage = () => {
// ignore the first message
edworker.initialize((ctx: worker.IWorkerContext, createData: ICreateData) => {
initialize((ctx: worker.IWorkerContext, createData: ICreateData) => {
return create(ctx, createData);
});
};
export { create } from './tsWorker';
export { TypeScriptWorker, create, initialize, libFileMap, ts };

@ -6,8 +6,8 @@
'npm/dev': 'node_modules/monaco-editor-core/dev/vs',
'npm/min': 'node_modules/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 = {
src: 'out/amd',
@ -35,11 +35,11 @@
div.style.padding = '5px 20px 5px 5px';
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);
@ -47,7 +47,7 @@
for (let i = 0; i < aElements.length; i++) {
let aElement = aElements[i];
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.
}
}
})();

@ -1,4 +1,4 @@
/// <reference path="../../release/monaco.d.ts" />
/// <reference path="../../out/monaco-editor/monaco.d.ts" />
define(['require', './samples'], function (require, SAMPLES) {
const domutils = require('vs/base/browser/dom');
@ -20,16 +20,10 @@ define(['require', './samples'], function (require, SAMPLES) {
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({
id: 'my-unique-id',

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

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

@ -30,8 +30,12 @@ export const pitch: PitchLoaderDefinitionFunction<ILoaderOptions> = function pit
...(globals
? 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');
};

@ -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>

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

@ -84,7 +84,7 @@ function loadScript(path: string): Promise<void> {
script.onload = () => res();
script.async = true;
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);
});
}

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { loadMonaco } from "../monaco-loader";
import { IMessage, IPreviewState } from "../shared";
import { IMessageFromRunner, IMessageToRunner, IPreviewState } from "../shared";
import "./style.scss";
window.addEventListener("message", (event) => {
@ -14,14 +14,14 @@ window.addEventListener("message", (event) => {
console.error("not in sandbox");
return;
}
const e = event.data as IMessage | { kind: undefined };
const e = event.data as IMessageToRunner | { kind: undefined };
if (e.kind === "initialize") {
initialize(e.state);
} else if (e.kind === "update-css") {
const style = document.getElementById(
"custom-style"
) 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");
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.innerHTML += state.html;
const js = massageJs(state.js);
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) {
const pre = document.createElement("pre");
pre.appendChild(
@ -61,3 +63,42 @@ async function initialize(state: IPreviewState) {
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;
}

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

@ -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>
);
};
}

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

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

@ -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 years 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! */}

@ -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({
original: originalModel,

@ -1,10 +1,10 @@
const text = `function hello() {
const value = /* set from `myEditor.getModel()`: */ `function hello() {
alert('Hello world!');
}`;
// 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",
automaticLayout: true,
});

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

@ -172,10 +172,9 @@ class EditorDemo extends React.Component {
<div>
<h2>IntelliSense, Validation</h2>
<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>
</div>
</div>
@ -184,10 +183,9 @@ class EditorDemo extends React.Component {
<div>
<h2>Basic Syntax Colorization</h2>
<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>
</div>
</div>
@ -217,6 +215,7 @@ class EditorDemo extends React.Component {
value={this.currentSample.value || "loading..."}
language={this.currentLanguage?.id}
theme={this.currentTheme.id}
onDidValueChange={() => {}}
/>
</div>
</div>

@ -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)
];
}
}

@ -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;
}
}
}

@ -8,7 +8,6 @@ import {
autorun,
computed,
observable,
ObservableMap,
reaction,
runInAction,
} from "mobx";
@ -18,22 +17,10 @@ import {
waitForLoadedMonaco,
} from "../../../monaco-loader";
import { IPlaygroundProject, IPreviewState } from "../../../shared";
import { monacoEditorVersion } from "../../monacoEditorVersion";
import { Debouncer } from "../../utils/Debouncer";
import { LzmaCompressor } from "../../utils/lzmaCompressor";
import {
HistoryController,
IHistoryModel,
ILocation,
} from "../../utils/ObservableHistory";
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 {
getDefaultSettings,
JsonString,
@ -41,6 +28,8 @@ import {
SettingsModel,
toLoaderConfig,
} from "./SettingsModel";
import { BisectModel } from "./BisectModel";
import { LocationModel } from "./LocationModel";
export class PlaygroundModel {
public readonly dispose = Disposable.fn();
@ -58,16 +47,18 @@ export class PlaygroundModel {
@observable
public reloadKey = 0;
public readonly serializer = new StateUrlSerializer(this);
public readonly historyModel = new LocationModel(this);
public reload(): void {
this.reloadKey++;
}
private readonly _previewHandlers = new Set<IPreviewHandler>();
public get previewShouldBeFullScreen(): boolean {
return this.settings.previewFullScreen;
}
private _wasEverNonFullScreen = false;
public get wasEverNonFullScreen() {
public get wasEverNonFullScreen(): boolean {
if (this._wasEverNonFullScreen) {
return true;
}
@ -79,7 +70,7 @@ export class PlaygroundModel {
@computed.struct
get monacoSetup(): IMonacoSetup {
const sourceOverride = this.serializer.sourceOverride;
const sourceOverride = this.historyModel.sourceOverride;
if (sourceOverride) {
return toLoaderConfig({
...getDefaultSettings(),
@ -105,10 +96,33 @@ export class PlaygroundModel {
return {
...this.playgroundProject,
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
public settingsDialogModel: SettingsDialogModel | undefined = undefined;
@ -134,43 +148,50 @@ export class PlaygroundModel {
example: value,
project: p,
};
this.reloadKey++;
this.setState(p);
});
});
}
}
private readonly debouncer = new Debouncer(250);
private readonly debouncer = new Debouncer(700);
@observable
public isDirty = false;
constructor() {
let lastState = this.state;
let lastState: IPreviewState | undefined = undefined;
this.dispose.track({
dispose: reaction(
() => ({ state: this.state }),
({ state }) => {
() => {
const state = this.state;
if (!this.settings.autoReload) {
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;
return;
}
}
this.debouncer.run(() => {
const updatePreviewState = () => {
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;
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 =
monaco.languages.typescript.javascriptDefaults.getCompilerOptions();
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 {
this.settingsDialogModel = new SettingsDialogModel(
this.settings.settings
@ -245,21 +299,13 @@ export class PlaygroundModel {
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);
}
export interface IPreviewHandler {
handlePreview(state: IPreviewState): void;
@action
compareWithLatestDev(): void {
this.settings.previewFullScreen = true;
this.historyModel.compareWithLatestDev();
}
}
export class SettingsDialogModel {
@ -277,419 +323,3 @@ export class SettingsDialogModel {
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,
};
}
}
}

@ -3,7 +3,19 @@ import * as React from "react";
import { hotComponent } from "../../utils/hotComponent";
import { PlaygroundModel } from "./PlaygroundModel";
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)
@observer
export class PlaygroundPage extends React.Component {

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

@ -1,23 +1,53 @@
import * as React from "react";
import { IPreviewHandler, PlaygroundModel } from "./PlaygroundModel";
import { PlaygroundModel } from "./PlaygroundModel";
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
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[] = [];
@observable
private counter = 0;
private currentState: IPreviewState | undefined;
@observable private counter = 0;
@observable.ref private currentState: IPreviewState | undefined;
private iframe: HTMLIFrameElement | null = null;
render() {
return (
<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
className="full-iframe"
key={this.counter}
@ -40,7 +70,7 @@ export class Preview
return;
}
const message: IMessage = {
const message: IMessageToRunner = {
kind: "initialize",
state: this.currentState,
};
@ -48,30 +78,47 @@ export class Preview
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() {
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() {
this.disposables.forEach((d) => d.dispose());
}
handlePreview(state: IPreviewState): void {
private handlePreview(state: IPreviewState): void {
if (
JSON.stringify({ ...state, css: "" }) ===
JSON.stringify({ ...this.currentState, css: "" })
) {
// only css changed
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;
} else {

@ -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,
};
}
}
}

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

@ -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");
}

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

@ -58,7 +58,7 @@ function createLangModel(languageId, text) {
var update = function () {
var def = null;
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) {
setInnerText(outputPane, err + "\n");
return;

@ -2147,10 +2147,10 @@ mobx@^5.15.4:
resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.15.7.tgz#b9a5f2b6251f5d96980d13c78e9b5d8d4ce22665"
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:
version "1.0.1"
@ -2828,14 +2828,14 @@ selfsigned@^2.0.1:
node-forge "^1"
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:
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:
lru-cache "^6.0.0"

Loading…
Cancel
Save