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