diff --git a/CHANGELOG.md b/CHANGELOG.md index 57537405..b4a60415 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Monaco Editor Changelog +## [0.37.1] + +- Fixes Inline Completions feature + ## [0.37.0] - New `registerLinkOpener` API diff --git a/package-lock.json b/package-lock.json index 081ac824..6702c553 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "monaco-editor", - "version": "0.37.0", + "version": "0.37.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "monaco-editor", - "version": "0.37.0", + "version": "0.37.1", "hasInstallScript": true, "license": "MIT", "devDependencies": { diff --git a/package.json b/package.json index 2696fdf5..68fb1602 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "monaco-editor", - "version": "0.37.0", - "vscodeRef": "9eba21c20f8720575cbc6c531d60c042c554d465", + "version": "0.37.1", + "vscodeRef": "8f74fbfd1f2d8f6268a42df131726b218aafe511", "private": true, "description": "A browser based code editor", "homepage": "https://github.com/microsoft/monaco-editor", diff --git a/website/scripts/check-playground-samples-js.ts b/website/scripts/check-playground-samples-js.ts index dffcc62e..b0fa28c4 100644 --- a/website/scripts/check-playground-samples-js.ts +++ b/website/scripts/check-playground-samples-js.ts @@ -14,6 +14,8 @@ import { exit } from "process"; "yarn", [ "tsc", + "--target", + "es6", "--noEmit", "--allowJs", "--checkJs", diff --git a/website/src/monaco-loader.ts b/website/src/monaco-loader.ts index acfe4035..2c771a0c 100644 --- a/website/src/monaco-loader.ts +++ b/website/src/monaco-loader.ts @@ -21,12 +21,24 @@ export interface IMonacoSetup { monacoTypesUrl: string | undefined; } -let loadMonacoPromise: Promise | undefined; +let loading = false; +let resolve: (value: typeof monaco) => void; +let reject: (error: unknown) => void; +let loadMonacoPromise = new Promise((res, rej) => { + resolve = res; + reject = rej; +}); + +export async function waitForLoadedMonaco(): Promise { + return loadMonacoPromise; +} + export async function loadMonaco( setup: IMonacoSetup = prodMonacoSetup ): Promise { - if (!loadMonacoPromise) { - loadMonacoPromise = _loadMonaco(setup); + if (!loading) { + loading = true; + _loadMonaco(setup).then(resolve, reject); } return loadMonacoPromise; } diff --git a/website/src/runner/index.ts b/website/src/runner/index.ts index 8bc551be..fe8af646 100644 --- a/website/src/runner/index.ts +++ b/website/src/runner/index.ts @@ -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,7 +14,7 @@ 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") { @@ -51,8 +51,10 @@ async function initialize(state: IPreviewState) { document.body.innerHTML += state.html; + const js = massageJs(state.js); + try { - eval(state.js); + eval(js); } 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; +} diff --git a/website/src/shared.ts b/website/src/shared.ts index 71036e39..807b30b7 100644 --- a/website/src/shared.ts +++ b/website/src/shared.ts @@ -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; diff --git a/website/src/website/data/playground-samples/creating-the-diffeditor/hello-diff-world/sample.js b/website/src/website/data/playground-samples/creating-the-diffeditor/hello-diff-world/sample.js index 83089074..84d8ba80 100644 --- a/website/src/website/data/playground-samples/creating-the-diffeditor/hello-diff-world/sample.js +++ b/website/src/website/data/playground-samples/creating-the-diffeditor/hello-diff-world/sample.js @@ -1,8 +1,18 @@ -var originalModel = monaco.editor.createModel("heLLo world!", "text/plain"); -var modifiedModel = monaco.editor.createModel("hello orlando!", "text/plain"); +const originalModel = monaco.editor.createModel( + /* set from `originalModel`: */ `hello world`, + "text/plain" +); +const modifiedModel = monaco.editor.createModel( + /* set from `modifiedModel`: */ `Hello World!`, + "text/plain" +); -var diffEditor = monaco.editor.createDiffEditor( - document.getElementById("container") +const diffEditor = monaco.editor.createDiffEditor( + document.getElementById("container"), + { + originalEditable: true, + automaticLayout: true, + } ); diffEditor.setModel({ original: originalModel, diff --git a/website/src/website/data/playground-samples/creating-the-editor/hello-world/sample.js b/website/src/website/data/playground-samples/creating-the-editor/hello-world/sample.js index c68ae059..7beb8e40 100644 --- a/website/src/website/data/playground-samples/creating-the-editor/hello-world/sample.js +++ b/website/src/website/data/playground-samples/creating-the-editor/hello-world/sample.js @@ -1,10 +1,10 @@ -const text = `function hello() { +const value = /* set from `myEditor.getModel()`: */ `function hello() { alert('Hello world!'); }`; // 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, }); diff --git a/website/src/website/data/playground-samples/creating-the-editor/web-component/sample.js b/website/src/website/data/playground-samples/creating-the-editor/web-component/sample.js index 07dc477f..9f178b30 100644 --- a/website/src/website/data/playground-samples/creating-the-editor/web-component/sample.js +++ b/website/src/website/data/playground-samples/creating-the-editor/web-component/sample.js @@ -11,10 +11,12 @@ customElements.define( const shadowRoot = this.attachShadow({ mode: "open" }); // Copy over editor styles - const style = document.querySelector( - "link[rel='stylesheet'][data-name='vs/editor/editor.main']" + const styles = document.querySelectorAll( + "link[rel='stylesheet'][data-name^='vs/']" ); - shadowRoot.appendChild(style.cloneNode(true)); + for (const style of styles) { + shadowRoot.appendChild(style.cloneNode(true)); + } const template = /** @type HTMLTemplateElement */ ( document.getElementById("editor-template") diff --git a/website/src/website/pages/playground/PlaygroundModel.ts b/website/src/website/pages/playground/PlaygroundModel.ts index 15ae15f8..51dd801c 100644 --- a/website/src/website/pages/playground/PlaygroundModel.ts +++ b/website/src/website/pages/playground/PlaygroundModel.ts @@ -12,7 +12,11 @@ import { reaction, runInAction, } from "mobx"; -import { IMonacoSetup, loadMonaco } from "../../../monaco-loader"; +import { + IMonacoSetup, + loadMonaco, + waitForLoadedMonaco, +} from "../../../monaco-loader"; import { IPlaygroundProject, IPreviewState } from "../../../shared"; import { monacoEditorVersion } from "../../monacoEditorVersion"; import { Debouncer } from "../../utils/Debouncer"; @@ -56,12 +60,23 @@ export class PlaygroundModel { public readonly serializer = new StateUrlSerializer(this); - reload(): void { + public reload(): void { this.reloadKey++; } private readonly _previewHandlers = new Set(); + private _wasEverNonFullScreen = false; + public get wasEverNonFullScreen() { + if (this._wasEverNonFullScreen) { + return true; + } + if (!this.settings.previewFullScreen) { + this._wasEverNonFullScreen = true; + } + return this._wasEverNonFullScreen; + } + @computed.struct get monacoSetup(): IMonacoSetup { const sourceOverride = this.serializer.sourceOverride; @@ -125,27 +140,61 @@ export class PlaygroundModel { } } - private readonly debouncer = new Debouncer(250); + private readonly debouncer = new Debouncer(700); + + @observable + public isDirty = false; constructor() { + let lastState = this.state; + this.dispose.track({ dispose: reaction( () => ({ state: this.state }), ({ state }) => { - this.debouncer.run(() => { + if (!this.settings.autoReload) { + if ( + JSON.stringify(state.monacoSetup) === + JSON.stringify(lastState.monacoSetup) && + state.key === lastState.key + ) { + this.isDirty = true; + return; + } + } + const action = () => { + this.isDirty = false; + lastState = state; for (const handler of this._previewHandlers) { handler.handlePreview(state); } - }); + }; + + if (state.key !== lastState.key) { + action(); // sync update + } else { + this.debouncer.run(action); + } }, { name: "update preview" } ), }); - const observablePromise = new ObservablePromise(loadMonaco()); + const observablePromise = new ObservablePromise(waitForLoadedMonaco()); let disposable: Disposable | undefined = undefined; - loadMonaco().then((m) => { + waitForLoadedMonaco().then((m) => { + this.dispose.track( + monaco.editor.addEditorAction({ + id: "reload", + label: "Reload", + run: (editor, ...args) => { + this.reload(); + }, + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter], + }) + ); + const options = monaco.languages.typescript.javascriptDefaults.getCompilerOptions(); monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions( @@ -190,6 +239,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|\\\\\\\\|\\\\`)*`" + ); + 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 diff --git a/website/src/website/pages/playground/PlaygroundPageContent.tsx b/website/src/website/pages/playground/PlaygroundPageContent.tsx index 7968b323..cdd953cf 100644 --- a/website/src/website/pages/playground/PlaygroundPageContent.tsx +++ b/website/src/website/pages/playground/PlaygroundPageContent.tsx @@ -18,7 +18,7 @@ import { PlaygroundModel } from "./PlaygroundModel"; import { Preview } from "./Preview"; import { SettingsDialog } from "./SettingsDialog"; import { Button, Col, Row, Stack } from "../../components/bootstrap"; -import { ButtonGroup } from "react-bootstrap"; +import { ButtonGroup, FormCheck } from "react-bootstrap"; @hotComponent(module) @observer @@ -37,88 +37,122 @@ export class PlaygroundPageContent extends React.Component< className="h-100 g-2" style={{ flexWrap: "wrap-reverse" }} > - - -
- - + +
+ - Example: - - - values={getPlaygroundExamples().map( - (e) => ({ - groupTitle: - e.chapterTitle, - items: e.examples, - }) - )} - value={ref( - model, - "selectedExample" - )} - getLabel={(i) => i.title} - /> -
- } - > - -
-
+ + Example: + + + values={getPlaygroundExamples().map( + (e) => ({ + groupTitle: + e.chapterTitle, + items: e.examples, + }) + )} + value={ref( + model, + "selectedExample" + )} + getLabel={(i) => + i.title + } + /> + + } + > + + + -
- - - -
+
+ + + +
-
- - - -
-
- +
+ + + +
+ + + )} + {model.settings.previewFullScreen || ( + { + model.settings.autoReload = + e.target.checked; + if ( + e.target.checked && + model.isDirty + ) { + model.reload(); + } + }} + /> + )}