mpy/alerts: implement compiler error

This shows the output from mpy-cross and has a button to go to the
error.
This commit is contained in:
David Lechner
2022-10-19 18:26:37 -05:00
committed by David Lechner
parent e5ab52b250
commit 7e726fffaf
10 changed files with 159 additions and 0 deletions
+2
View File
@@ -6,6 +6,7 @@ import alerts from './alerts/alerts';
import ble from './ble/alerts';
import explorer from './explorer/alerts';
import firmware from './firmware/alerts';
import mpy from './mpy/alerts';
import type { CreateToast } from './toasterTypes';
/** This collects alerts from all of the subsystems of the app */
@@ -14,6 +15,7 @@ const alertDomains = {
ble,
explorer,
firmware,
mpy,
};
/** Gets the type of available alert domains. */
+9
View File
@@ -112,3 +112,12 @@ export const editorDidFailToActivateFile = createAction((uuid: UUID, error: Erro
uuid,
error,
}));
/**
* Requests to activate a file and show line.
*/
export const editorGoto = createAction((uuid: UUID, line: number) => ({
type: 'editor.action.goto',
uuid,
line,
}));
+30
View File
@@ -60,6 +60,7 @@ import {
editorDidOpenFile,
editorGetValueRequest,
editorGetValueResponse,
editorGoto,
editorOpenFile,
} from './actions';
import { EditorError } from './error';
@@ -265,6 +266,34 @@ function* handleEditorActivateFile(
}
}
function* handleEditorGoto(
editor: monaco.editor.ICodeEditor,
action: ReturnType<typeof editorGoto>,
): Generator {
yield* put(editorActivateFile(action.uuid));
const { didActivate, didFailToActivate } = yield* race({
didActivate: take(editorDidActivateFile.when((a) => a.uuid === action.uuid)),
didFailToActivate: take(
editorDidFailToActivateFile.when((a) => a.uuid === action.uuid),
),
});
if (didFailToActivate) {
return;
}
defined(didActivate);
editor.revealLineInCenterIfOutsideViewport(action.line);
editor.setSelection({
startColumn: 1,
startLineNumber: action.line,
endColumn: Infinity,
endLineNumber: action.line,
});
}
function* handleEditorDidCloseFile(
activeFileHistory: ActiveFileHistoryManager,
action: ReturnType<typeof editorDidCloseFile>,
@@ -356,6 +385,7 @@ function* handleDidCreateEditor(editor: monaco.editor.ICodeEditor): Generator {
openFiles,
activeFileHistory,
);
yield* takeEvery(editorGoto, handleEditorGoto, editor);
yield* takeEvery(editorDidCloseFile, handleEditorDidCloseFile, activeFileHistory);
yield* fork(monitorViewState, editor);
+15
View File
@@ -25,3 +25,18 @@ export function useFileStoragePath(uuid: UUID): string | undefined {
const db = useContext(FileStorageContext);
return useLiveQuery(() => db.metadata.get(uuid, (x) => x?.path));
}
/**
* Gets the file path for a file UUID.
*
* If the file is renamed, the returned value will be automatically updated.
*/
export function useFileStorageUuid(path: string): UUID | undefined {
const db = useContext(FileStorageContext);
return useLiveQuery(() =>
db.metadata
.where('path')
.equals(path)
.first((x) => x?.uuid),
);
}
+10
View File
@@ -206,6 +206,16 @@ function* handleDownloadAndRun(action: ReturnType<typeof downloadAndRun>): Gener
});
if (didFailToCompile) {
yield* put(
alertsShowAlert(
'mpy',
'compilerError',
{
error: didFailToCompile.error,
},
'mpy.compilerError',
),
);
return;
}
+58
View File
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
import { Button, Intent } from '@blueprintjs/core';
import React, { useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { editorGoto } from '../../editor/actions';
import { useFileStorageUuid } from '../../fileStorage/hooks';
import type { CreateToast } from '../../toasterTypes';
import { useI18n } from './i18n';
type CompilerErrorProps = {
error: string[];
};
const CompilerError: React.VoidFunctionComponent<CompilerErrorProps> = ({ error }) => {
const dispatch = useDispatch();
const i18n = useI18n();
const [file, line] = useMemo(() => {
for (const line of error) {
const match = line.match(/^ {2}File "(.*)", line (\d+)/);
if (match) {
return [match[1], Number(match[2])];
}
}
return [undefined, undefined];
}, [error]);
const uuid = useFileStorageUuid(file ?? '');
return (
<>
<p>{i18n.translate('compilerError.message')}</p>
<pre className="pb-mpy-alerts-compile-error">{error.join('\n')}</pre>
{file && uuid && (
<Button icon="code" onClick={() => dispatch(editorGoto(uuid, line))}>
{i18n.translate('compilerError.gotoErrorButton')}
</Button>
)}
</>
);
};
export const compilerError: CreateToast<CompilerErrorProps, 'dismiss' | 'gotoError'> = (
onAction,
props,
) => {
return {
message: <CompilerError {...props} />,
icon: 'error',
intent: Intent.DANGER,
timeout: 0,
onDismiss: () => onAction('dismiss'),
};
};
+12
View File
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
import type { TypedI18n } from '../../i18n';
import type translations from './translations/en.json';
export function useI18n(): TypedI18n<typeof translations> {
// istanbul ignore next: babel-loader rewrites this line
const [i18n] = useShopifyI18n();
return i18n;
}
+7
View File
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
pre.pb-mpy-alerts-compile-error {
white-space: pre-wrap;
word-break: keep-all;
}
+10
View File
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
import './index.scss';
import { compilerError } from './CompilerError';
// gathers all of the alert creation functions for passing up to the top level
export default {
compilerError,
};
+6
View File
@@ -0,0 +1,6 @@
{
"compilerError": {
"message": "Failed to compile MicroPython file.",
"gotoErrorButton": "Goto error"
}
}