From d95c5502489c984da542731aadad1e64dbe53d2f Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 2 Jul 2021 18:33:35 -0500 Subject: [PATCH] editor: change from ace to monaco This replaces the ace editor with the monaco editor. It fixes quite a few small paper-cut bugs and should be easier to add code completion to in the future. Many of the snippets are omitted but can be added back later if needed. Otherwise, we have tried to preserve the existing look and feel as much as possible. --- .vscode/launch.json | 4 +- CHANGELOG.md | 3 + craco.config.js | 19 ++ package.json | 6 +- src/editor/Editor.test.tsx | 2 +- src/editor/Editor.tsx | 218 +++++++++------- src/editor/actions.ts | 8 +- src/editor/editor.scss | 26 +- src/editor/i18n.en.json | 2 + src/editor/i18n.ts | 4 +- src/editor/pybricksMicroPython.ts | 406 ++++++++++++++++++++++++++++++ src/editor/reducers.test.ts | 4 +- src/editor/reducers.ts | 9 +- src/editor/sagas.test.ts | 12 +- src/editor/snippets.test.ts | 21 -- src/editor/snippets.ts | 170 ------------- src/editor/untitledHint.ts | 90 +++++++ src/hub/sagas.test.ts | 6 +- src/react-app-env.d.ts | 5 + yarn.lock | 129 +++++++--- 20 files changed, 790 insertions(+), 354 deletions(-) create mode 100644 src/editor/pybricksMicroPython.ts delete mode 100644 src/editor/snippets.test.ts delete mode 100644 src/editor/snippets.ts create mode 100644 src/editor/untitledHint.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 6a555a21..1ed621d3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,9 +5,9 @@ "version": "0.2.0", "configurations": [ { - "type": "chrome", + "name": "Launch Chrome", "request": "launch", - "name": "Launch Chrome against localhost", + "type": "pwa-chrome", "url": "http://localhost:3000", "webRoot": "${workspaceFolder}", "linux": { diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d7a118..c2a357dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ## [Unreleased] +## Changed +- Changed from Ace editor to Monaco editor. + ### Fixed - Fixed run button still enabled after hub disconnects during download [support#378]. diff --git a/craco.config.js b/craco.config.js index a413ce2a..d4a6bfea 100644 --- a/craco.config.js +++ b/craco.config.js @@ -10,6 +10,7 @@ const { } = require('@craco/craco'); const CopyPlugin = require('copy-webpack-plugin'); const LicensePlugin = require('license-webpack-plugin').LicenseWebpackPlugin; +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const satisfies = require('spdx-satisfies'); // Permissive licenses can be added here. We would like to avoid copyleft. @@ -234,6 +235,10 @@ module.exports = { unacceptableLicenseTest: (licenseType) => !satisfies(licenseType, `(${approvedLicenses.join(' OR ')})`), }), + new MonacoWebpackPlugin({ + languages: ['python'], + filename: '[name].worker.[contenthash].js', + }), ], configure: { resolve: { @@ -290,6 +295,20 @@ module.exports = { return webpackConfig; }, + overrideJestConfig: ({ + jestConfig, + cracoConfig, + pluginOptions, + context: { env, paths, resolve, rootDir }, + }) => { + const index = jestConfig.transformIgnorePatterns.indexOf( + '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$', + ); + // https://github.com/react-monaco-editor/react-monaco-editor/issues/306#issuecomment-701025628 + jestConfig.transformIgnorePatterns[index] = + '[/\\\\]node_modules[/\\\\](?!(monaco-editor|react-monaco-editor)[/\\\\]).+\\.(js|jsx|mjs|cjs|ts|tsx)$'; + return jestConfig; + }, }, options: {}, }, diff --git a/package.json b/package.json index 986595ce..4787190a 100644 --- a/package.json +++ b/package.json @@ -28,19 +28,21 @@ "@types/redux-logger": "^3.0.8", "@types/web-bluetooth": "^0.0.9", "@types/zen-push": "^0.1.1", - "ace-builds": "^1.4.12", "babel-plugin-macros": "^3.0.1", + "canvas": "^2.8.0", "copy-webpack-plugin": "^6.4.1", "file-saver": "^2.0.5", "jszip": "^3.6.0", "license-webpack-plugin": "^2.3.15", + "monaco-editor-webpack-plugin": "^4.0.0", + "monaco-themes": "^0.3.3", "mq-polyfill": "^1.1.8", "node-sass": "^6.0.1", "prop-types": "^15.7.2", "react": "^16.13.1", - "react-ace": "^9.3.0", "react-dom": "^16.13.1", "react-dropzone": "^11.3.4", + "react-monaco-editor": "^0.43.0", "react-redux": "^7.2.2", "react-scripts": "^4.0.3", "react-splitter-layout": "^4.0.0", diff --git a/src/editor/Editor.test.tsx b/src/editor/Editor.test.tsx index e2e98a54..6856eecf 100644 --- a/src/editor/Editor.test.tsx +++ b/src/editor/Editor.test.tsx @@ -84,7 +84,7 @@ describe('context menu', () => { expect(screen.getByText('Copy')).toBeInTheDocument(); - userEvent.type(document.activeElement || document.body, '{esc}'); + userEvent.type(screen.getByText('Copy'), '{esc}'); await waitForElementToBeRemoved(() => screen.queryByText('Copy')); diff --git a/src/editor/Editor.tsx b/src/editor/Editor.tsx index e6eaf9c7..2a73f764 100644 --- a/src/editor/Editor.tsx +++ b/src/editor/Editor.tsx @@ -3,11 +3,12 @@ import { Menu, MenuDivider, MenuItem, ResizeSensor } from '@blueprintjs/core'; import { WithI18nProps, withI18n } from '@shopify/react-i18n'; -import { Ace, config } from 'ace-builds'; +import tomorrowNightEightiesTheme from 'monaco-themes/themes/Tomorrow-Night-Eighties.json'; +import xcodeTheme from 'monaco-themes/themes/Xcode_default.json'; import React from 'react'; -import AceEditor from 'react-ace'; -import { IAceEditor } from 'react-ace/lib/types'; +import MonacoEditor, { monaco } from 'react-monaco-editor'; import { connect } from 'react-redux'; +import { IDisposable } from 'xterm'; import { compile } from '../mpy/actions'; import { RootState } from '../reducers'; import { toggleBoolean } from '../settings/actions'; @@ -17,15 +18,9 @@ import { isMacOS } from '../utils/os'; import { setEditSession, storageChanged } from './actions'; import { EditorStringId } from './i18n'; import en from './i18n.en.json'; +import * as pybricksMicroPython from './pybricksMicroPython'; +import { UntitledHintContribution } from './untitledHint'; -import 'ace-builds/src-noconflict/mode-python'; -import 'ace-builds/src-noconflict/theme-tomorrow_night_eighties'; -import 'ace-builds/src-noconflict/theme-xcode'; -import 'ace-builds/src-noconflict/ext-searchbox'; -import 'ace-builds/src-noconflict/ext-keybinding_menu'; -import 'ace-builds/src-noconflict/ext-language_tools'; - -import './snippets'; import './editor.scss'; type StateProps = { @@ -34,7 +29,7 @@ type StateProps = { }; type DispatchProps = { - onSessionChanged: (session?: Ace.EditSession) => void; + onSessionChanged: (session?: monaco.editor.ICodeEditor) => void; onProgramStorageChanged: (newValue: string) => void; onCheck: (script: string) => void; onToggleDocs: () => void; @@ -42,17 +37,49 @@ type DispatchProps = { type EditorProps = StateProps & DispatchProps & WithI18nProps; +const pybricksMicroPythonId = 'pybricks-micropython'; +monaco.languages.register({ id: pybricksMicroPythonId }); + +const toDispose = new Array(); +toDispose.push( + monaco.languages.setMonarchTokensProvider( + pybricksMicroPythonId, + pybricksMicroPython.language, + ), +); +toDispose.push( + monaco.languages.registerCompletionItemProvider( + pybricksMicroPythonId, + pybricksMicroPython.completions, + ), +); + +// https://webpack.js.org/api/hot-module-replacement/ +if (module.hot) { + module.hot.dispose(() => { + toDispose.forEach((s) => s.dispose()); + }); +} + +const tomorrowNightEightiesId = 'tomorrow-night-eighties'; +monaco.editor.defineTheme( + tomorrowNightEightiesId, + tomorrowNightEightiesTheme as monaco.editor.IStandaloneThemeData, +); + +const xcodeId = 'xcode'; +monaco.editor.defineTheme(xcodeId, xcodeTheme as monaco.editor.IStandaloneThemeData); + class Editor extends React.Component implements IContextMenuTarget { - private editorRef: React.RefObject; - private keyBindings?: Array<{ key: string; command: string }>; + private editorRef: React.RefObject; constructor(props: EditorProps) { super(props); this.editorRef = React.createRef(); } - /** convenience property for getting Ace editor object */ - private get editor(): IAceEditor | undefined { + /** convenience property for getting editor object */ + private get editor(): monaco.editor.IStandaloneCodeEditor | undefined { return this.editorRef.current?.editor; } @@ -78,77 +105,61 @@ class Editor extends React.Component implements IContextMenuTarget const { i18n, darkMode, onSessionChanged, onCheck, onToggleDocs } = this.props; return (
handleContextMenu(e, this)}> - this.editor?.resize()}> - this.editor?.layout()}> + { - // default binding is F2 which conflicts with 'check' - e.commands.byName['toggleFoldWidget'].bindKey = { - win: 'Shift-F2', - mac: 'Shift-F2', - }; - - // we want to use Ctrl-D for docs toggle, so change - // delete line to VSCode default - e.commands.byName['removeline'].bindKey = { - win: 'Ctrl-Shift-K', - mac: 'Cmd-Shift-K', - }; - - config.loadModule( - 'ace/ext/menu_tools/get_editor_keyboard_shortcuts', - (m) => { - this.keyBindings = m.getEditorKeybordShortcuts(e); + value={localStorage.getItem('program')} + editorDidMount={(e, _m): void => { + // FIXME: editor does not respond to changes in i18n + const untitledHintContribution = + new UntitledHintContribution( + e, + i18n.translate(EditorStringId.Placeholder), + ); + e.onDidDispose(() => untitledHintContribution.dispose()); + e.addAction({ + id: 'pybricks.action.toggleDocs', + label: i18n.translate(EditorStringId.ToggleDocs), + run: () => onToggleDocs(), + keybindings: [ + monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_D, + ], + }); + e.addAction({ + id: 'pybricks.action.check', + label: i18n.translate(EditorStringId.Check), + run: () => onCheck(e.getValue()), + keybindings: [monaco.KeyCode.F2], + }); + e.addAction({ + id: 'pybricks.action.save', + label: 'Unused', + run: () => { + // We already automatically save the file + // to local storage after every change, so + // CTRL+S is ignored + console.debug('Ctrl-S ignored'); }, - ); - - config.loadModule('ace/ext/keybinding_menu', (m) => - m.init(e), - ); - }} - onFocus={(_, e): void => { - onSessionChanged(e?.session); + keybindings: [ + monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, + ], + }); + e.focus(); + onSessionChanged(e); }} onChange={(v): void => { localStorage.setItem('program', v); }} - commands={[ - { - // command to check current program for errors - name: 'check', - bindKey: { win: 'F2', mac: 'F2' }, - exec: (editor) => onCheck(editor.getValue()), - }, - { - name: 'toggleDocs', - bindKey: { win: 'Ctrl-D', mac: 'Cmd-D' }, - exec: () => onToggleDocs(), - }, - { - name: 'save', - bindKey: { win: 'Ctrl-S', mac: 'Cmd-S' }, - exec: (): void => { - // We already automatically save the file - // to local storage after every change, so - // CTRL+S is ignored - console.debug('CTRL+S ignored'); - }, - }, - ]} />
@@ -161,21 +172,28 @@ class Editor extends React.Component implements IContextMenuTarget { - const selected = this.editor?.getSelectedText(); - if (selected) { - navigator.clipboard.writeText(selected); - } + this.editor?.focus(); + this.editor?.trigger( + null, + 'editor.action.clipboardCopyAction', + null, + ); }} text={i18n.translate(EditorStringId.Copy)} icon="duplicate" label={isMacOS() ? 'Cmd-C' : 'Ctrl-C'} - disabled={this.editor?.getSelection().isEmpty()} + disabled={ + !this.editor?.getSelection() || + this.editor?.getSelection()?.isEmpty() + } /> => { - this.editor?.execCommand( - 'paste', - await navigator.clipboard.readText(), + this.editor?.focus(); + this.editor?.trigger( + null, + 'editor.action.clipboardPasteAction', + null, ); }} text={i18n.translate(EditorStringId.Paste)} @@ -183,33 +201,43 @@ class Editor extends React.Component implements IContextMenuTarget label={isMacOS() ? 'Cmd-V' : 'Ctrl-V'} /> this.editor?.selectAll()} + onClick={() => { + this.editor?.focus(); + this.editor?.trigger(null, 'editor.action.selectAll', null); + }} text={i18n.translate(EditorStringId.SelectAll)} icon="blank" label={isMacOS() ? 'Cmd-A' : 'Ctrl-A'} /> this.editor?.undo()} + onClick={(): void => { + this.editor?.focus(); + this.editor?.trigger(null, 'undo', null); + }} text={i18n.translate(EditorStringId.Undo)} icon="undo" - label={this.keyBindings?.find((x) => x.command === 'undo')?.key} - disabled={!this.editor?.session.getUndoManager().canUndo()} + label={isMacOS() ? 'Cmd-Z' : 'Ctrl-Z'} + // @ts-expect-error internal method canUndo() + disabled={!this.editor?.getModel()?.canUndo()} /> this.editor?.redo()} + onClick={(): void => { + this.editor?.focus(); + this.editor?.trigger(null, 'redo', null); + }} text={i18n.translate(EditorStringId.Redo)} icon="redo" - label={this.keyBindings?.find((x) => x.command === 'redo')?.key} - active - disabled={!this.editor?.session.getUndoManager().canRedo()} + label={isMacOS() ? 'Cmd-Shift-Z' : 'Ctrl-Shift-Z'} + // @ts-expect-error internal method canUndo() + disabled={!this.editor?.getModel()?.canRedo()} /> ); } onContextMenuClose = () => { - this.editorRef.current?.editor.focus(); + this.editor?.focus(); }; } diff --git a/src/editor/actions.ts b/src/editor/actions.ts index f26bd6fe..5c031c47 100644 --- a/src/editor/actions.ts +++ b/src/editor/actions.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020 The Pybricks Authors +// Copyright (c) 2020-2021 The Pybricks Authors -import { Ace } from 'ace-builds'; +import { monaco } from 'react-monaco-editor'; import { Action } from 'redux'; export enum EditorActionType { @@ -28,7 +28,7 @@ export enum EditorActionType { } export type CurrentEditorAction = Action & { - editSession: Ace.EditSession | undefined; + editSession: monaco.editor.ICodeEditor | undefined; }; /** @@ -36,7 +36,7 @@ export type CurrentEditorAction = Action & { * @param editSession The new edit session. */ export function setEditSession( - editSession: Ace.EditSession | undefined, + editSession: monaco.editor.ICodeEditor | undefined, ): CurrentEditorAction { return { type: EditorActionType.Current, editSession }; } diff --git a/src/editor/editor.scss b/src/editor/editor.scss index 910048bd..f51ba515 100644 --- a/src/editor/editor.scss +++ b/src/editor/editor.scss @@ -5,25 +5,19 @@ @import '../variables.scss'; -// make ace editor match app backgound color +// make editor match app backgound color -.#{$ns}-dark .ace_gutter { +.#{$ns}-dark .margin-view-overlays { background-color: $pt-dark-app-background-color; } -.ace_gutter { +.margin-view-overlays { background-color: $pt-app-background-color; } -// higher contrast - -.ace-tomorrow-night-eighties .ace_print-margin { - background: $dark-gray4; -} - // add "BETA" watermark -.pb-beta .ace_scroller::after { +.pb-beta .editor-scrollable::after { content: ''; background: url('./beta.svg'); opacity: 1; @@ -34,3 +28,15 @@ position: absolute; pointer-events: none; } + +.#{$ns}-dark .pb-editor-placeholder { + color: $pt-dark-text-color-muted; +} + +.pb-editor-placeholder { + pointer-events: none; + width: max-content; + color: $pt-text-color-muted; + font-style: italic; + padding-left: 4px; +} diff --git a/src/editor/i18n.en.json b/src/editor/i18n.en.json index ba9898fa..288ef667 100644 --- a/src/editor/i18n.en.json +++ b/src/editor/i18n.en.json @@ -1,6 +1,8 @@ { "editor": { "placeholder": "Write your program here...", + "check": "Check Syntax", + "toggleDocs": "Toggle Documentation", "copy": "Copy", "paste": "Paste", "selectAll": "Select All", diff --git a/src/editor/i18n.ts b/src/editor/i18n.ts index 7e6d044b..8f9902d5 100644 --- a/src/editor/i18n.ts +++ b/src/editor/i18n.ts @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020 The Pybricks Authors +// Copyright (c) 2020-2021 The Pybricks Authors // // Editor translation keys. export enum EditorStringId { Placeholder = 'editor.placeholder', + Check = 'editor.check', + ToggleDocs = 'editor.toggleDocs', Copy = 'editor.copy', Paste = 'editor.paste', SelectAll = 'editor.selectAll', diff --git a/src/editor/pybricksMicroPython.ts b/src/editor/pybricksMicroPython.ts new file mode 100644 index 00000000..2865df04 --- /dev/null +++ b/src/editor/pybricksMicroPython.ts @@ -0,0 +1,406 @@ +// Copied from https://github.com/microsoft/monaco-languages/blob/main/src/python/python.ts + +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { monaco } from 'react-monaco-editor'; + +export const language = { + defaultToken: '', + tokenPostfix: '.python', + + keywords: [ + // This section is the result of running + // `for k in keyword.kwlist: print(' "' + k + '",')` in a Python REPL, + // though note that the output from Python 3 is not a strict superset of the + // output from Python 2. + 'False', // promoted to keyword.kwlist in Python 3 + 'None', // promoted to keyword.kwlist in Python 3 + 'True', // promoted to keyword.kwlist in Python 3 + 'and', + 'as', + 'assert', + 'async', // new in Python 3 + 'await', // new in Python 3 + 'break', + 'class', + 'continue', + 'def', + 'del', + 'elif', + 'else', + 'except', + 'exec', // Python 2, but not 3. + 'finally', + 'for', + 'from', + 'global', + 'if', + 'import', + 'in', + 'is', + 'lambda', + 'nonlocal', // new in Python 3 + 'not', + 'or', + 'pass', + 'print', // Python 2, but not 3. + 'raise', + 'return', + 'try', + 'while', + 'with', + 'yield', + + 'int', + 'float', + 'long', + 'complex', + 'hex', + + 'abs', + 'all', + 'any', + 'apply', + 'basestring', + 'bin', + 'bool', + 'buffer', + 'bytearray', + 'callable', + 'chr', + 'classmethod', + 'cmp', + 'coerce', + 'compile', + 'complex', + 'delattr', + 'dict', + 'dir', + 'divmod', + 'enumerate', + 'eval', + 'execfile', + 'file', + 'filter', + 'format', + 'frozenset', + 'getattr', + 'globals', + 'hasattr', + 'hash', + 'help', + 'id', + 'input', + 'intern', + 'isinstance', + 'issubclass', + 'iter', + 'len', + 'locals', + 'list', + 'map', + 'max', + 'memoryview', + 'min', + 'next', + 'object', + 'oct', + 'open', + 'ord', + 'pow', + 'print', + 'property', + 'reversed', + 'range', + 'raw_input', + 'reduce', + 'reload', + 'repr', + 'reversed', + 'round', + 'self', + 'set', + 'setattr', + 'slice', + 'sorted', + 'staticmethod', + 'str', + 'sum', + 'super', + 'tuple', + 'type', + 'unichr', + 'unicode', + 'vars', + 'xrange', + 'zip', + + '__dict__', + '__methods__', + '__members__', + '__class__', + '__bases__', + '__name__', + '__mro__', + '__subclasses__', + '__init__', + '__import__', + ], + + brackets: [ + { open: '{', close: '}', token: 'delimiter.curly' }, + { open: '[', close: ']', token: 'delimiter.bracket' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, + ], + + tokenizer: { + root: [ + { include: '@whitespace' }, + { include: '@numbers' }, + { include: '@strings' }, + { include: '@operators' }, + + [/[,:;]/, 'delimiter'], + [/[{}[\]()]/, '@brackets'], + + [/@[a-zA-Z_]\w*/, 'tag'], + + [ + /[a-zA-Z_]\w*/, + { + cases: { + '@keywords': 'keyword', + '@default': 'identifier', + }, + }, + ], + ], + + // Deal with white space, including single and multi-line comments + whitespace: [ + [/\s+/, 'white'], + [/(^#.*$)/, 'comment'], + [/'''/, 'string', '@endDocString'], + [/"""/, 'string', '@endDblDocString'], + ], + endDocString: [ + [/[^']+/, 'string'], + [/\\'/, 'string'], + [/'''/, 'string', '@popall'], + [/'/, 'string'], + ], + endDblDocString: [ + [/[^"]+/, 'string'], + [/\\"/, 'string'], + [/"""/, 'string', '@popall'], + [/"/, 'string'], + ], + + // Recognize hex, negatives, decimals, imaginaries, longs, and scientific notation + numbers: [ + [/-?0x([abcdef]|[ABCDEF]|\d)+[lL]?/, 'constant.numeric.hex'], + [/-?(\d*\.)?\d+([eE][+-]?\d+)?[jJ]?[lL]?/, 'constant.numeric'], + ], + + // Recognize strings, including those broken across lines with \ (but not without) + strings: [ + [/'$/, 'string.escape', '@popall'], + [/'/, 'string.escape', '@stringBody'], + [/"$/, 'string.escape', '@popall'], + [/"/, 'string.escape', '@dblStringBody'], + ], + stringBody: [ + [/[^\\']+$/, 'string', '@popall'], + [/[^\\']+/, 'string'], + [/\\./, 'string'], + [/'/, 'string.escape', '@popall'], + [/\\$/, 'string'], + ], + dblStringBody: [ + [/[^\\"]+$/, 'string', '@popall'], + [/[^\\"]+/, 'string'], + [/\\./, 'string'], + [/"/, 'string.escape', '@popall'], + [/\\$/, 'string'], + ], + + operators: [[/[=+\-*/%@&|<>!~^]/, 'keyword.operator']], + + attributes: [ + [/\b/, '@pop'], + [/[a-zA-Z_]\w*/, 'attribute'], + ], + }, +}; + +export const completions = { + provideCompletionItems: (_model, position, _context, _token) => { + return { + suggestions: [ + { + label: 'technichub', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: `from pybricks.hubs import TechnicHub +from pybricks.pupdevices import Motor +from pybricks.parameters import Port, Stop, Color +from pybricks.tools import wait + +hub = TechnicHub()`, + range: monaco.Range.fromPositions(position), + }, + { + label: 'cityhub', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: `from pybricks.hubs import CityHub +from pybricks.pupdevices import Motor +from pybricks.parameters import Port, Stop, Color +from pybricks.tools import wait + +hub = CityHub()`, + range: monaco.Range.fromPositions(position), + }, + { + label: 'movehub', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: `from pybricks.hubs import MoveHub +from pybricks.pupdevices import Motor +from pybricks.parameters import Port, Stop, Color +from pybricks.tools import wait + +hub = MoveHub()`, + range: monaco.Range.fromPositions(position), + }, + { + label: 'primehub', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: `from pybricks.hubs import PrimeHub +from pybricks.pupdevices import Motor, ColorSensor, ForceSensor, UltrasonicSensor +from pybricks.parameters import Port, Stop, Color, Button +from pybricks.tools import wait + +hub = PrimeHub()`, + range: monaco.Range.fromPositions(position), + }, + { + label: 'inventorhub', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: `from pybricks.hubs import InventorHub +from pybricks.pupdevices import Motor, ColorSensor, UltrasonicSensor +from pybricks.parameters import Port, Stop, Color, Button +from pybricks.tools import wait + +hub = InventorHub()`, + range: monaco.Range.fromPositions(position), + }, + ], + }; + }, +}; + +// old snippets from ace editor +// eslint-disable-next-line +const _unused = ` +snippet imp + import \${1:module} +snippet from + from \${1:package} import \${2:module} +# Module Docstring +snippet docs + ''' + File: \${1:FILENAME:file_name} + Author: \${2:author} + Date: \${3:date} + Description: \${4} + ''' +snippet wh + while \${1:condition}: + \${2:# TODO: write code...} +# dowh - does the same as do...while in other languages +snippet dowh + while True: + \${1:# TODO: write code...} + if \${2:condition}: + break +snippet with + with \${1:expr} as \${2:var}: + \${3:# TODO: write code...} +# New Class +snippet cl + class \${1:ClassName}(\${2:object}): + """\${3:docstring for $1}""" + def __init__(self, \${4:arg}): + \${5:super($1, self).__init__()} + self.$4 = $4 + \${6} +# New Function +snippet def + def \${1:fname}(\${2:\`indent('.') ? 'self' : ''\`}): + """\${3:docstring for $1}""" + \${4:# TODO: write code...} +snippet deff + def \${1:fname}(\${2:\`indent('.') ? 'self' : ''\`}): + \${3:# TODO: write code...} +# New Method +snippet defs + def \${1:mname}(self, \${2:arg}): + \${3:# TODO: write code...} +# New Property +snippet property + @property + def \${1:pname}(): + \${2:return self._$1} +# Ifs +snippet if + if \${1:condition}: + \${2:# TODO: write code...} +snippet el + else: + \${1:# TODO: write code...} +snippet ei + elif \${1:condition}: + \${2:# TODO: write code...} +# For +snippet for + for \${1:item} in \${2:items}: + \${3:# TODO: write code...} +# Lambda +snippet ld + \${1:var} = lambda \${2:vars} : \${3:action} +snippet . + self. +snippet try Try/Except + try: + \${1:# TODO: write code...} + except \${2:Exception} as \${3:e}: + \${4:raise $3} +snippet try Try/Except/Else + try: + \${1:# TODO: write code...} + except \${2:Exception} as \${3:e}: + \${4:raise $3} + else: + \${5:# TODO: write code...} +snippet try Try/Except/Finally + try: + \${1:# TODO: write code...} + except \${2:Exception} as \${3:e}: + \${4:raise $3} + finally: + \${5:# TODO: write code...} +snippet try Try/Except/Else/Finally + try: + \${1:# TODO: write code...} + except \${2:Exception} as \${3:e}: + \${4:raise $3} + else: + \${5:# TODO: write code...} + finally: + \${6:# TODO: write code...} +snippet " + """ + \${1:doc} + """ +`; diff --git a/src/editor/reducers.test.ts b/src/editor/reducers.test.ts index b53531bc..142962b3 100644 --- a/src/editor/reducers.test.ts +++ b/src/editor/reducers.test.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2021 The Pybricks Authors -import { Ace } from 'ace-builds'; +import { monaco } from 'react-monaco-editor'; import { Action } from '../actions'; import { setEditSession } from './actions'; import reducers from './reducers'; @@ -17,7 +17,7 @@ test('initial state', () => { }); test('current', () => { - const session = {} as Ace.EditSession; + const session = {} as monaco.editor.ICodeEditor; expect(reducers({ current: null } as State, setEditSession(session)).current).toBe( session, ); diff --git a/src/editor/reducers.ts b/src/editor/reducers.ts index 76f89da0..a2e5fb82 100644 --- a/src/editor/reducers.ts +++ b/src/editor/reducers.ts @@ -1,12 +1,15 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020 The Pybricks Authors +// Copyright (c) 2020-2021 The Pybricks Authors -import { Ace } from 'ace-builds'; +import { monaco } from 'react-monaco-editor'; import { Reducer, combineReducers } from 'redux'; import { Action } from '../actions'; import { EditorActionType } from './actions'; -const current: Reducer = (state = null, action) => { +const current: Reducer = ( + state = null, + action, +) => { switch (action.type) { case EditorActionType.Current: return action.editSession || null; diff --git a/src/editor/sagas.test.ts b/src/editor/sagas.test.ts index 3e26a5e0..49c6d9d1 100644 --- a/src/editor/sagas.test.ts +++ b/src/editor/sagas.test.ts @@ -1,17 +1,17 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020 The Pybricks Authors +// Copyright (c) 2020-2021 The Pybricks Authors -import { Ace } from 'ace-builds'; import { mock } from 'jest-mock-extended'; +import { monaco } from 'react-monaco-editor'; import { AsyncSaga } from '../../test'; import { open, reloadProgram, saveAs } from './actions'; import editor from './sagas'; -jest.mock('ace-builds'); +jest.mock('react-monaco-editor'); jest.mock('file-saver'); test('open', async () => { - const mockEditor = mock(); + const mockEditor = mock(); const saga = new AsyncSaga(editor, { editor: { current: mockEditor } }); const data = new Uint8Array().buffer; @@ -23,7 +23,7 @@ test('open', async () => { }); test('saveAs', async () => { - const mockEditor = mock(); + const mockEditor = mock(); const saga = new AsyncSaga(editor, { editor: { current: mockEditor } }); saga.put(saveAs()); @@ -34,7 +34,7 @@ test('saveAs', async () => { }); test('reloadProgram', async () => { - const mockEditor = mock(); + const mockEditor = mock(); const saga = new AsyncSaga(editor, { editor: { current: mockEditor } }); saga.put(reloadProgram()); diff --git a/src/editor/snippets.test.ts b/src/editor/snippets.test.ts deleted file mode 100644 index 5ddf13cb..00000000 --- a/src/editor/snippets.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2021 The Pybricks Authors - -import ace from 'ace-builds'; - -import './snippets'; - -type Snippets = { - snippetText: string; - scope: string; -}; - -it('indents with tabs', () => { - const { snippetText, scope } = ace.require('ace/snippets/python') as Snippets; - - expect(scope).toBe('python'); - - for (const line of snippetText.split('\n')) { - expect(line).not.toMatch(/^(\t* )+\t*/); - } -}); diff --git a/src/editor/snippets.ts b/src/editor/snippets.ts deleted file mode 100644 index 31ff508d..00000000 --- a/src/editor/snippets.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import ace from 'ace-builds'; - -// fixups for Ace editor bindings -declare module 'ace-builds' { - export function define( - module: string, - deps: string[], - payload?: (require: any, exports: any, module: any) => void, - ): void; - export function require(name: string[], callback: (module: any) => void): any; -} - -ace.define( - 'ace/snippets/python', - ['require', 'exports', 'module'], - function (_require, exports, _module) { - // IMPORTANT!!!!! - // Snippets must be indented with tab character, not spaces! - exports.snippetText = `snippet technichub - from pybricks.hubs import TechnicHub - from pybricks.pupdevices import Motor - from pybricks.parameters import Port, Stop, Color - from pybricks.tools import wait - - hub = TechnicHub() -snippet cityhub - from pybricks.hubs import CityHub - from pybricks.pupdevices import Motor - from pybricks.parameters import Port, Stop, Color - from pybricks.tools import wait - - hub = CityHub() -snippet movehub - from pybricks.hubs import MoveHub - from pybricks.pupdevices import Motor - from pybricks.parameters import Port, Stop, Color - from pybricks.tools import wait - - hub = MoveHub() -snippet primehub - from pybricks.hubs import PrimeHub - from pybricks.pupdevices import Motor, ColorSensor, ForceSensor, UltrasonicSensor - from pybricks.parameters import Port, Stop, Color, Button - from pybricks.tools import wait - - hub = PrimeHub() -snippet inventorhub - from pybricks.hubs import InventorHub - from pybricks.pupdevices import Motor, ColorSensor, UltrasonicSensor - from pybricks.parameters import Port, Stop, Color, Button - from pybricks.tools import wait - - hub = InventorHub() -snippet uname - from uos import uname - - print(uname()) -snippet imp - import \${1:module} -snippet from - from \${1:package} import \${2:module} -# Module Docstring -snippet docs - ''' - File: \${1:FILENAME:file_name} - Author: \${2:author} - Date: \${3:date} - Description: \${4} - ''' -snippet wh - while \${1:condition}: - \${2:# TODO: write code...} -# dowh - does the same as do...while in other languages -snippet dowh - while True: - \${1:# TODO: write code...} - if \${2:condition}: - break -snippet with - with \${1:expr} as \${2:var}: - \${3:# TODO: write code...} -# New Class -snippet cl - class \${1:ClassName}(\${2:object}): - """\${3:docstring for $1}""" - def __init__(self, \${4:arg}): - \${5:super($1, self).__init__()} - self.$4 = $4 - \${6} -# New Function -snippet def - def \${1:fname}(\${2:\`indent('.') ? 'self' : ''\`}): - """\${3:docstring for $1}""" - \${4:# TODO: write code...} -snippet deff - def \${1:fname}(\${2:\`indent('.') ? 'self' : ''\`}): - \${3:# TODO: write code...} -# New Method -snippet defs - def \${1:mname}(self, \${2:arg}): - \${3:# TODO: write code...} -# New Property -snippet property - @property - def \${1:pname}(): - \${2:return self._$1} -# Ifs -snippet if - if \${1:condition}: - \${2:# TODO: write code...} -snippet el - else: - \${1:# TODO: write code...} -snippet ei - elif \${1:condition}: - \${2:# TODO: write code...} -# For -snippet for - for \${1:item} in \${2:items}: - \${3:# TODO: write code...} -# Lambda -snippet ld - \${1:var} = lambda \${2:vars} : \${3:action} -snippet . - self. -snippet try Try/Except - try: - \${1:# TODO: write code...} - except \${2:Exception} as \${3:e}: - \${4:raise $3} -snippet try Try/Except/Else - try: - \${1:# TODO: write code...} - except \${2:Exception} as \${3:e}: - \${4:raise $3} - else: - \${5:# TODO: write code...} -snippet try Try/Except/Finally - try: - \${1:# TODO: write code...} - except \${2:Exception} as \${3:e}: - \${4:raise $3} - finally: - \${5:# TODO: write code...} -snippet try Try/Except/Else/Finally - try: - \${1:# TODO: write code...} - except \${2:Exception} as \${3:e}: - \${4:raise $3} - else: - \${5:# TODO: write code...} - finally: - \${6:# TODO: write code...} -snippet " - """ - \${1:doc} - """ -`; - exports.scope = 'python'; - }, -); - -(function (): void { - ace.require(['ace/snippets/python'], function (m: any) { - if (typeof module == 'object' && typeof exports == 'object' && module) { - module.exports = m; - } - }); -})(); diff --git a/src/editor/untitledHint.ts b/src/editor/untitledHint.ts new file mode 100644 index 00000000..87355850 --- /dev/null +++ b/src/editor/untitledHint.ts @@ -0,0 +1,90 @@ +// Copied from https://github.com/microsoft/vscode/blob/167b197e767beb791e3d191c24efb97865a98b42/src/vs/workbench/browser/parts/editor/untitledHint.ts +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { monaco } from 'react-monaco-editor'; + +export class UntitledHintContribution implements monaco.editor.IEditorContribution { + public static readonly ID = 'editor.contrib.untitledHint'; + + private toDispose: monaco.IDisposable[]; + private untitledHintContentWidget: UntitledHintContentWidget | undefined; + + constructor( + private readonly editor: monaco.editor.ICodeEditor, + private readonly placeholder: string, + ) { + this.toDispose = []; + this.toDispose.push(editor.onDidChangeModel(() => this.update())); + this.update(); + } + + private update(): void { + this.untitledHintContentWidget?.dispose(); + this.untitledHintContentWidget = new UntitledHintContentWidget( + this.editor, + this.placeholder, + ); + } + + dispose(): void { + this.toDispose.forEach((d) => d.dispose()); + this.untitledHintContentWidget?.dispose(); + } +} + +class UntitledHintContentWidget implements monaco.editor.IContentWidget { + private static readonly ID = 'editor.widget.untitledHint'; + + private domNode: HTMLElement | undefined; + private toDispose: monaco.IDisposable[]; + + constructor( + private readonly editor: monaco.editor.ICodeEditor, + private readonly placeholder: string, + ) { + this.toDispose = []; + this.toDispose.push( + editor.onDidChangeModelContent(() => this.onDidChangeModelContent()), + ); + this.onDidChangeModelContent(); + } + + private onDidChangeModelContent(): void { + if (this.editor.getValue() === '') { + this.editor.addContentWidget(this); + } else { + this.editor.removeContentWidget(this); + } + } + + getId(): string { + return UntitledHintContentWidget.ID; + } + + // Select a language to get started. Start typing to dismiss, or don't show this again. + getDomNode(): HTMLElement { + if (!this.domNode) { + this.domNode = document.createElement('div'); + this.domNode.textContent = this.placeholder; + this.domNode.className = 'pb-editor-placeholder'; + this.editor.applyFontInfo(this.domNode); + } + + return this.domNode; + } + + getPosition(): monaco.editor.IContentWidgetPosition | null { + return { + position: { lineNumber: 1, column: 1 }, + preference: [monaco.editor.ContentWidgetPositionPreference.EXACT], + }; + } + + dispose(): void { + this.editor.removeContentWidget(this); + this.toDispose.forEach((d) => d.dispose()); + } +} diff --git a/src/hub/sagas.test.ts b/src/hub/sagas.test.ts index 1d3cd2db..0528b4f2 100644 --- a/src/hub/sagas.test.ts +++ b/src/hub/sagas.test.ts @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2021 The Pybricks Authors -import { Ace } from 'ace-builds'; import { mock } from 'jest-mock-extended'; +import { monaco } from 'react-monaco-editor'; import { AsyncSaga } from '../../test'; import { BlePybricksServiceCommandActionType, @@ -22,11 +22,11 @@ import { } from './actions'; import hub from './sagas'; -jest.mock('ace-builds'); +jest.mock('react-monaco-editor'); describe('downloadAndRun', () => { test('no errors', async () => { - const mockEditor = mock(); + const mockEditor = mock(); const saga = new AsyncSaga( hub, { editor: { current: mockEditor } }, diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index e1cd6f56..8d1cc02f 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -14,3 +14,8 @@ declare module '*.zip' { const src: string; export default src; } + +// https://webpack.js.org/api/hot-module-replacement/ +interface NodeModule { + hot?: { dispose: (callback: (data) => void) => void }; +} diff --git a/yarn.lock b/yarn.lock index c5cd9c44..f8178f04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1507,6 +1507,21 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950" + integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA== + dependencies: + detect-libc "^1.0.3" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.1" + nopt "^5.0.0" + npmlog "^4.1.2" + rimraf "^3.0.2" + semver "^7.3.4" + tar "^6.1.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2485,11 +2500,6 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -ace-builds@^1.4.12: - version "1.4.12" - resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.12.tgz#888efa386e36f4345f40b5233fcc4fe4c588fae7" - integrity sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg== - acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -3550,6 +3560,15 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, can resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001239.tgz#66e8669985bb2cb84ccb10f68c25ce6dd3e4d2b8" integrity sha512-cyBkXJDMeI4wthy8xJ2FvDU6+0dtcZSJW3voUF8+e9f1bBeuvyZfc3PNbkOETyhbR+dGCPzn9E7MA3iwzusOhQ== +canvas@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.8.0.tgz#f99ca7f25e6e26686661ffa4fec1239bbef74461" + integrity sha512-gLTi17X8WY9Cf5GZ2Yns8T5lfBOcGgFehDFb+JQwDqdOoBOcECS9ZWMEAqMSVcMYwXD659J8NyzjRY/2aE+C2Q== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + nan "^2.14.0" + simple-get "^3.0.3" + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -4460,6 +4479,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -4570,6 +4596,11 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -4588,11 +4619,6 @@ detect-port-alt@1.1.6: address "^1.0.1" debug "^2.6.0" -diff-match-patch@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37" - integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== - diff-sequences@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" @@ -5507,6 +5533,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-plist@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" + integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= + fastq@^1.6.0: version "1.11.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" @@ -7739,16 +7770,6 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= - -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -7854,7 +7875,7 @@ make-dir@^2.0.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0, make-dir@^3.0.2: +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -8035,6 +8056,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -8153,6 +8179,25 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +monaco-editor-webpack-plugin@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.0.0.tgz#95be3f48f4220999b909266a9997727f0deab947" + integrity sha512-4BT9XDRQXraMQjxEUjR+uuubRe3RIPkvVoGw8zwWG++s7wq6TAiXaSOMdkdS9TrjCREgSnygCOlVzY6MS8RPuA== + dependencies: + loader-utils "^2.0.0" + +monaco-editor@*: + version "0.25.2" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.25.2.tgz#119e2b15bbd968a1a99c03cac9c329316d7c37e9" + integrity sha512-5iylzSJevCnzJn9UVsW8yOZ3yHjmAs4TfvH3zsbftKiFKmHG0xirGN6DK9Kk04VSWxYCZZAIafYJoNJJMAU1KA== + +monaco-themes@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/monaco-themes/-/monaco-themes-0.3.3.tgz#86d9b7cde4d90acb128ec07b3d5d10f757a32224" + integrity sha512-0yygYHg3hZhMhlYkP2eMAMurLSyVDqA7ZOjHJSz0LNxra2hxuAGjKFdoBGXnG+VykT6u8bc5uZWF8G35NMC3Gw== + dependencies: + fast-plist "^0.1.2" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -8203,7 +8248,7 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -nan@^2.12.1, nan@^2.13.2: +nan@^2.12.1, nan@^2.13.2, nan@^2.14.0: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== @@ -8277,6 +8322,11 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -9972,17 +10022,6 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -react-ace@^9.3.0: - version "9.4.1" - resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-9.4.1.tgz#5f446c2764a0b615e8d00cd251b9b35d95365e37" - integrity sha512-vhOFrB5Xy++mcPNc6wc1mwMFP/FZOnYxQPqgRl/dLfkZBbrJf4SAgXaa6PU4AXWu1u5bfxOmRwwHaZPrLb6d9Q== - dependencies: - ace-builds "^1.4.12" - diff-match-patch "^1.0.4" - lodash.get "^4.4.2" - lodash.isequal "^4.5.0" - prop-types "^15.7.2" - react-app-polyfill@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf" @@ -10064,6 +10103,14 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-monaco-editor@^0.43.0: + version "0.43.0" + resolved "https://registry.yarnpkg.com/react-monaco-editor/-/react-monaco-editor-0.43.0.tgz#495578470db7b27ab306af813b31f206a6bf9d1c" + integrity sha512-2PXSDwOzjUDlwdsK8psrmezSB2lPnjLQ/SRNwqoRF2+TAB2n4Q+Bs1wGlWOZbU8WI7s6Wnni0+3PdT9nSPtzRA== + dependencies: + monaco-editor "*" + prop-types "^15.7.2" + react-popper@^1.3.7: version "1.3.11" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.11.tgz#a2cc3f0a67b75b66cfa62d2c409f9dd1fcc71ffd" @@ -10955,6 +11002,20 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -11581,7 +11642,7 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar@^6.0.2: +tar@^6.0.2, tar@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==