123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- import * as React from "react";
- import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
- require("../scss/monaco.scss");
- interface IMonacoComponentProps {
- language: "JS" | "TS";
- }
- export class MonacoComponent extends React.Component<IMonacoComponentProps> {
- private _hostReference: React.RefObject<HTMLDivElement>;
- private _editor: monaco.editor.IStandaloneCodeEditor;
- private _definitionWorker: Worker;
- private _deprecatedCandidates: string[];
- // private _templates: string[];
-
- public constructor(props: IMonacoComponentProps) {
- super(props);
- this._hostReference = React.createRef();
- }
- async setupMonaco() {
- let hostElement = this._hostReference.current!;
- var editorOptions: monaco.editor.IEditorConstructionOptions = {
- value: "",
- language: this.props.language == "JS" ? "javascript" : "typescript",
- lineNumbers: "on",
- roundedSelection: true,
- automaticLayout: true,
- scrollBeyondLastLine: false,
- readOnly: false,
- theme: "vs",
- contextmenu: false,
- folding: true,
- showFoldingControls: "always",
- renderIndentGuides: true,
- minimap: {
- enabled: true
- }
- };
- this._editor = monaco.editor.create(
- hostElement,
- editorOptions
- );
- let response = await fetch("https://preview.babylonjs.com/babylon.d.ts");
- if (!response.ok) {
- return;
- }
- let libContent = await response.text();
- response = await fetch("https://preview.babylonjs.com/gui/babylon.gui.d.ts");
- if (!response.ok) {
- return;
- }
- libContent += await response.text();
- this.setupDefinitionWorker(libContent);
- // Load code templates
- response = await fetch("/templates.json");
- if (response.ok) {
- // this._templates = await response.json();
- }
- // Setup the Monaco compilation pipeline, so we can reuse it directly for our scrpting needs
- this.setupMonacoCompilationPipeline(libContent);
- // This is used for a vscode-like color preview for ColorX types
- this.setupMonacoColorProvider();
- }
- // Provide an adornment for BABYLON.ColorX types: color preview
- setupMonacoColorProvider() {
- monaco.languages.registerColorProvider(this.props.language == "JS" ? "javascript" : "typescript", {
- provideColorPresentations: (model, colorInfo) => {
- const color = colorInfo.color;
- const precision = 100.0;
- const converter = (n: number) => Math.round(n * precision) / precision;
- let label;
- if (color.alpha === undefined || color.alpha === 1.0) {
- label = `(${converter(color.red)}, ${converter(color.green)}, ${converter(color.blue)})`;
- } else {
- label = `(${converter(color.red)}, ${converter(color.green)}, ${converter(color.blue)}, ${converter(color.alpha)})`;
- }
- return [{
- label: label
- }];
- },
- provideDocumentColors: (model) => {
- const digitGroup = "\\s*(\\d*(?:\\.\\d+)?)\\s*";
- // we add \n{0} to workaround a Monaco bug, when setting regex options on their side
- const regex = `BABYLON\\.Color(?:3|4)\\s*\\(${digitGroup},${digitGroup},${digitGroup}(?:,${digitGroup})?\\)\\n{0}`;
- const matches = model.findMatches(regex, false, true, true, null, true);
- const converter = (g: string) => g === undefined ? undefined : Number(g);
- return matches.map(match => ({
- color: {
- red: converter(match.matches![1])!,
- green: converter(match.matches![2])!,
- blue: converter(match.matches![3])!,
- alpha: converter(match.matches![4])!
- },
- range: {
- startLineNumber: match.range.startLineNumber,
- startColumn: match.range.startColumn + match.matches![0].indexOf("("),
- endLineNumber: match.range.startLineNumber,
- endColumn: match.range.endColumn
- }
- }));
- }
- });
- }
- // Setup both JS and TS compilation pipelines to work with our scripts.
- setupMonacoCompilationPipeline(libContent: string) {
- const typescript = monaco.languages.typescript;
- if (this.props.language == "JS") {
- typescript.javascriptDefaults.setCompilerOptions({
- noLib: false,
- allowNonTsExtensions: true // required to prevent Uncaught Error: Could not find file: 'inmemory://model/1'.
- });
- typescript.javascriptDefaults.addExtraLib(libContent, 'babylon.d.ts');
- } else {
- typescript.typescriptDefaults.setCompilerOptions({
- module: typescript.ModuleKind.AMD,
- target: typescript.ScriptTarget.ESNext,
- noLib: false,
- strict: false,
- alwaysStrict: false,
- strictFunctionTypes: false,
- suppressExcessPropertyErrors: false,
- suppressImplicitAnyIndexErrors: true,
- noResolve: true,
- suppressOutputPathCheck: true,
- allowNonTsExtensions: true // required to prevent Uncaught Error: Could not find file: 'inmemory://model/1'.
- });
- typescript.typescriptDefaults.addExtraLib(libContent, 'babylon.d.ts');
- }
- }
- setupDefinitionWorker(libContent: string) {
- this._definitionWorker = new Worker('workers/definitionWorker.js');
- this._definitionWorker.addEventListener('message', ({
- data
- }) => {
- this._deprecatedCandidates = data.result;
- this.analyzeCode();
- });
- this._definitionWorker.postMessage({
- code: libContent
- });
- }
- // This will make sure that all members marked with a deprecated jsdoc attribute will be marked as such in Monaco UI
- // We use a prefiltered list of deprecated candidates, because the effective call to getCompletionEntryDetails is slow.
- // @see setupDefinitionWorker
- async analyzeCode() {
- // if the definition worker is very fast, this can be called out of context. @see setupDefinitionWorker
- if (!this._editor)
- return;
- const model = this._editor.getModel();
- if (!model)
- return;
- const uri = model.uri;
- let worker = null;
- if (this.props.language == "JS")
- worker = await monaco.languages.typescript.getJavaScriptWorker();
- else
- worker = await monaco.languages.typescript.getTypeScriptWorker();
- const languageService = await worker(uri);
- const source = '[deprecated members]';
- monaco.editor.setModelMarkers(model, source, []);
- const markers: {
- startLineNumber: number,
- endLineNumber: number,
- startColumn: number,
- endColumn: number,
- message: string,
- severity: monaco.MarkerSeverity.Warning,
- source: string,
- }[] = [];
- for (const candidate of this._deprecatedCandidates) {
- const matches = model.findMatches(candidate, false, false, true, null, false);
- for (const match of matches) {
- const position = {
- lineNumber: match.range.startLineNumber,
- column: match.range.startColumn
- };
- const wordInfo = model.getWordAtPosition(position);
- const offset = model.getOffsetAt(position);
- if (!wordInfo) {
- continue;
- }
- // continue if we already found an issue here
- if (markers.find(m => m.startLineNumber == position.lineNumber && m.startColumn == position.column))
- continue;
- // the following is time consuming on all suggestions, that's why we precompute deprecated candidate names in the definition worker to filter calls
- // @see setupDefinitionWorker
- const details = await languageService.getCompletionEntryDetails(uri.toString(), offset, wordInfo.word);
- if (this.isDeprecatedEntry(details)) {
- const deprecatedInfo = details.tags.find(this.isDeprecatedTag);
- markers.push({
- startLineNumber: match.range.startLineNumber,
- endLineNumber: match.range.endLineNumber,
- startColumn: wordInfo.startColumn,
- endColumn: wordInfo.endColumn,
- message: deprecatedInfo.text,
- severity: monaco.MarkerSeverity.Warning,
- source: source,
- });
- }
- }
- }
- monaco.editor.setModelMarkers(model, source, markers);
- }
- isDeprecatedEntry(details: any) {
- return details &&
- details.tags &&
- details.tags.find(this.isDeprecatedTag);
- }
- isDeprecatedTag(tag: any) {
- return tag &&
- tag.name == "deprecated";
- }
- componentDidMount() {
- this.setupMonaco();
- }
- public render() {
- return (
- <div id="monacoHost" ref={this._hostReference}>
- </div>
- )
- }
- }
|