From 9efd1d7cd6c11a1e0a79aaa1fc2cf3ef1adab21b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 18 Mar 2020 18:53:35 +0100 Subject: [PATCH] Improves error messages. --- .vscode/launch.json | 8 +- demos/php/.vscode/launch.json | 2 +- demos/php/.vscode/settings.json | 2 +- ...ConfiguredDataExtractionProviderFactory.ts | 4 + .../DataExtractionProvider.ts | 3 +- .../GenericDataExtractionProviderFactory.ts | 22 +++++- .../JsDataExtractionProviderFactory.ts | 7 +- ...EvaluationResultFromGenericDebugAdapter.ts | 48 ++++++++++-- extension/src/contract.ts | 17 +++- webview/src/components/ExpressionInput.tsx | 5 +- webview/src/components/NoData.tsx | 78 ++++++++++++++----- webview/src/components/Visualizer.tsx | 43 ++++++++-- webview/src/style.scss | 17 ++-- yarn.lock | 5 -- 14 files changed, 203 insertions(+), 58 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6ecaca6..1e926e1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceFolder}/extension", - "${workspaceFolder}\\demo" + "${workspaceFolder}\\demos\\js" ], "env": { "HOT_RELOAD": "true", @@ -24,7 +24,7 @@ "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceFolder}/extension", - "${workspaceFolder}\\demo" + "${workspaceFolder}\\demos\\php" ], "env": { "HOT_RELOAD": "true", @@ -40,7 +40,7 @@ "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceFolder}/extension", - "${workspaceFolder}\\demo" + "${workspaceFolder}\\demos\\js" ], "env": { "HOT_RELOAD": "", @@ -55,7 +55,7 @@ "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceFolder}/extension", - "${workspaceFolder}\\demo" + "${workspaceFolder}\\demos\\php" ], "env": { "HOT_RELOAD": "", diff --git a/demos/php/.vscode/launch.json b/demos/php/.vscode/launch.json index 5bbcd70..d221356 100644 --- a/demos/php/.vscode/launch.json +++ b/demos/php/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "PHP - Demo", "type": "php", "request": "launch", - "program": "${workspaceFolder}/php/demo.php", + "program": "${workspaceFolder}/demo.php", "xdebugSettings": { "max_data": -1 } diff --git a/demos/php/.vscode/settings.json b/demos/php/.vscode/settings.json index 46508d1..b6694d6 100644 --- a/demos/php/.vscode/settings.json +++ b/demos/php/.vscode/settings.json @@ -1,6 +1,6 @@ { "debugVisualizer.debugAdapterConfigurations": { - "php": { + "phpa": { "context": "watch", "expressionTemplate": "${expr}" } diff --git a/extension/src/DataSource/DataExtractionProvider/ConfiguredDataExtractionProviderFactory.ts b/extension/src/DataSource/DataExtractionProvider/ConfiguredDataExtractionProviderFactory.ts index 1e720f2..a385316 100644 --- a/extension/src/DataSource/DataExtractionProvider/ConfiguredDataExtractionProviderFactory.ts +++ b/extension/src/DataSource/DataExtractionProvider/ConfiguredDataExtractionProviderFactory.ts @@ -6,7 +6,11 @@ import { } from "./DataExtractionProvider"; import { Config, DebugAdapterConfig } from "../../Config"; import { GenericDataExtractionProvider } from "./GenericDataExtractionProviderFactory"; +import { registerUpdateReconciler, hotClass } from "@hediet/node-reload"; +registerUpdateReconciler(module); + +@hotClass(module) export class ConfiguredDataExtractionProviderFactory implements DataExtractionProviderFactory { constructor(private readonly config: Config) {} diff --git a/extension/src/DataSource/DataExtractionProvider/DataExtractionProvider.ts b/extension/src/DataSource/DataExtractionProvider/DataExtractionProvider.ts index aed30bd..12fd609 100644 --- a/extension/src/DataSource/DataExtractionProvider/DataExtractionProvider.ts +++ b/extension/src/DataSource/DataExtractionProvider/DataExtractionProvider.ts @@ -3,6 +3,7 @@ import { DataExtractorId, } from "@hediet/debug-visualizer-data-extraction"; import { VsCodeDebugSession } from "../../VsCodeDebugger"; +import { FormattedMessage } from "../../contract"; export interface DataExtractionProviderFactory { createDataExtractionProvider( @@ -15,7 +16,7 @@ export interface DataExtractionProvider { args: DataExtractionProviderArgs ): Promise< | { kind: "data"; result: DataExtractionResult } - | { kind: "error"; message: string } + | { kind: "error"; message: FormattedMessage } >; } diff --git a/extension/src/DataSource/DataExtractionProvider/GenericDataExtractionProviderFactory.ts b/extension/src/DataSource/DataExtractionProvider/GenericDataExtractionProviderFactory.ts index 14e1e0e..f0d5674 100644 --- a/extension/src/DataSource/DataExtractionProvider/GenericDataExtractionProviderFactory.ts +++ b/extension/src/DataSource/DataExtractionProvider/GenericDataExtractionProviderFactory.ts @@ -9,7 +9,12 @@ import { DataExtractionProviderArgs, } from "./DataExtractionProvider"; import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter"; +import { FormattedMessage } from "../../contract"; +import { hotClass, registerUpdateReconciler } from "@hediet/node-reload"; +registerUpdateReconciler(module); + +@hotClass(module) export class GenericDataExtractionProviderFactory implements DataExtractionProviderFactory { createDataExtractionProvider( @@ -28,7 +33,7 @@ export class GenericDataExtractionProvider implements DataExtractionProvider { frameId, }: DataExtractionProviderArgs): Promise< | { kind: "data"; result: DataExtractionResult } - | { kind: "error"; message: string } + | { kind: "error"; message: FormattedMessage } > { const finalExpression = this.getFinalExpression({ expression, @@ -44,7 +49,20 @@ export class GenericDataExtractionProvider implements DataExtractionProvider { } catch (error) { return { kind: "error", - message: error.message, + message: { + kind: "list", + items: [ + error.message, + `Used debug adapter: ${this.session.session.configuration.type}`, + { + kind: "inlineList", + items: [ + "Evaluated expression is", + { kind: "code", content: finalExpression }, + ], + }, + ], + }, }; } diff --git a/extension/src/DataSource/DataExtractionProvider/JsDataExtractionProviderFactory.ts b/extension/src/DataSource/DataExtractionProvider/JsDataExtractionProviderFactory.ts index c26a92e..026f1b3 100644 --- a/extension/src/DataSource/DataExtractionProvider/JsDataExtractionProviderFactory.ts +++ b/extension/src/DataSource/DataExtractionProvider/JsDataExtractionProviderFactory.ts @@ -11,7 +11,12 @@ import { DataExtractionProvider, DataExtractionProviderArgs, } from "./DataExtractionProvider"; +import { FormattedMessage } from "../../contract"; +import { registerUpdateReconciler, hotClass } from "@hediet/node-reload"; +registerUpdateReconciler(module); + +@hotClass(module) export class JsDataExtractionProviderFactory implements DataExtractionProviderFactory { createDataExtractionProvider( @@ -39,7 +44,7 @@ class JsDataEvaluator implements DataExtractionProvider { frameId, }: DataExtractionProviderArgs): Promise< | { kind: "data"; result: DataExtractionResult } - | { kind: "error"; message: string } + | { kind: "error"; message: FormattedMessage } > { while (true) { try { diff --git a/extension/src/DataSource/DataExtractionProvider/parseEvaluationResultFromGenericDebugAdapter.ts b/extension/src/DataSource/DataExtractionProvider/parseEvaluationResultFromGenericDebugAdapter.ts index 213534b..43d57b2 100644 --- a/extension/src/DataSource/DataExtractionProvider/parseEvaluationResultFromGenericDebugAdapter.ts +++ b/extension/src/DataSource/DataExtractionProvider/parseEvaluationResultFromGenericDebugAdapter.ts @@ -2,12 +2,13 @@ import { DataExtractionResult, isExtractedData, } from "@hediet/debug-visualizer-data-extraction"; +import { FormattedMessage } from "../../contract"; export function parseEvaluationResultFromGenericDebugAdapter( resultText: string ): | { kind: "data"; result: DataExtractionResult } - | { kind: "error"; message: string } { + | { kind: "error"; message: FormattedMessage } { const jsonData = resultText.trim(); let resultObj; @@ -37,7 +38,26 @@ export function parseEvaluationResultFromGenericDebugAdapter( if (!isExtractedData(resultObj)) { return { kind: "error", - message: "Data does not match ExtractedData interface.", + message: { + kind: "list", + items: [ + "Evaluation result does not match ExtractedData interface.", + { + kind: "inlineList", + items: [ + "Evaluation result was:", + { + kind: "code", + content: JSON.stringify( + resultObj, + undefined, + 4 + ), + }, + ], + }, + ], + }, }; } } catch (e) { @@ -69,8 +89,26 @@ function parseJson(str: string) { try { return JSON.parse(str); } catch (error) { - throw new Error( - `Could not parse \`${str}\` as JSON.\n${error.message}` - ); + throw new FormattedError({ + kind: "list", + items: [ + "Could not parse evaluation result as JSON:", + error.message, + { + kind: "inlineList", + items: [ + "Evaluation result was:", + { + kind: "code", + content: str, + }, + ], + }, + ], + }); } } + +class FormattedError { + constructor(public readonly message: FormattedMessage) {} +} diff --git a/extension/src/contract.ts b/extension/src/contract.ts index 5e8edf1..d0a9bba 100644 --- a/extension/src/contract.ts +++ b/extension/src/contract.ts @@ -18,9 +18,24 @@ function unchecked(): types.Type { ); } +export type FormattedMessage = + | string + | { + kind: "list"; + items: FormattedMessage[]; + } + | { + kind: "inlineList"; + items: FormattedMessage[]; + } + | { + kind: "code"; + content: string; + }; + export type DataExtractionState = | { kind: "loading" } - | { kind: "error"; message: string } + | { kind: "error"; message: FormattedMessage } | { kind: "noDebugSession" } | { kind: "data"; diff --git a/webview/src/components/ExpressionInput.tsx b/webview/src/components/ExpressionInput.tsx index 955b399..7619fb8 100644 --- a/webview/src/components/ExpressionInput.tsx +++ b/webview/src/components/ExpressionInput.tsx @@ -76,11 +76,8 @@ export class ExpressionInput extends React.Component<{ model: Model }> { horizontalScrollbarSize: 0, verticalScrollbarSize: 0, }, - /*theme: { dark: "vs-dark", light: "vs-light" }[ - this.props.model.theme - ],*/ }); - // + this.editor.onDidContentSizeChange(e => { this.contentHeight = e.contentHeight; }); diff --git a/webview/src/components/NoData.tsx b/webview/src/components/NoData.tsx index ad6b2b3..0b2564b 100644 --- a/webview/src/components/NoData.tsx +++ b/webview/src/components/NoData.tsx @@ -4,41 +4,79 @@ import { observable } from "mobx"; import Measure from "react-measure"; @observer -export class NoData extends React.Component<{ label: string }> { - @observable height = 0; - @observable width = 0; +export class NoData extends React.Component<{ children: React.ReactChild }> { + @observable size = { width: 0, height: 0 }; + @observable innerSize = { width: 0, height: 0 }; render() { - const { width, height } = this; + const { width, height } = this.size; + const { width: innerWidth, height: innerHeight } = this.innerSize; return ( { if (e.client) { - this.height = e.client.height; - this.width = e.client.width; + this.size = { + height: e.client.height, + width: e.client.width, + }; } }} > {({ measureRef }) => ( -
+
- - +
+ { + if (e.client) { + this.innerSize = { + height: e.client.height, + width: e.client.width, + }; + } + }} + > + {({ measureRef }) => ( +
+ {this.props.children} +
+ )} +
+
)} diff --git a/webview/src/components/Visualizer.tsx b/webview/src/components/Visualizer.tsx index c52a7ff..cf590a2 100644 --- a/webview/src/components/Visualizer.tsx +++ b/webview/src/components/Visualizer.tsx @@ -2,6 +2,7 @@ import React = require("react"); import { Model } from "../model/Model"; import { observer } from "mobx-react"; import { NoData } from "./NoData"; +import { FormattedMessage } from "debug-visualizer/src/contract"; @observer export class Visualizer extends React.Component<{ model: Model }> { @@ -14,17 +15,21 @@ export class Visualizer extends React.Component<{ model: Model }> { renderContent(): JSX.Element { const s = this.props.model.state; if (s.kind === "loading") { - return ; + return Loading; } else if (s.kind === "error") { - return ; + return ( + + + + ); } else if (s.kind === "noExpression") { - return ; + return No Expression Entered; } else if (s.kind === "noDebugSession") { - return ; + return No Active Debug Session; } else if (s.kind === "data") { const vis = this.props.model.visualizations; if (!vis || !vis.visualization) { - return ; + return No Visualization Available; } return vis.visualization.render(); @@ -34,3 +39,31 @@ export class Visualizer extends React.Component<{ model: Model }> { } } } + +function Message(props: { message: FormattedMessage }): React.ReactElement { + if (typeof props.message === "string") { + return {props.message}; + } else if (props.message.kind === "list") { + return ( +
+ {props.message.items.map((i, idx) => ( +

+ +

+ ))} +
+ ); + } else if (props.message.kind === "inlineList") { + return ( +
+ {props.message.items.map((i, idx) => ( + + ))} +
+ ); + } else if (props.message.kind === "code") { + return
{props.message.content}
; + } + + throw new Error("Bug"); +} diff --git a/webview/src/style.scss b/webview/src/style.scss index 8b7cc6a..00b88e0 100644 --- a/webview/src/style.scss +++ b/webview/src/style.scss @@ -139,18 +139,19 @@ body { width: 100%; height: 100%; - rect { - fill: var(--vscode-editor-background); - } - line { stroke: gray; } + } - text { - fill: var(--vscode-editor-foreground); - stroke-width: 0px; - } + .part-content-inner { + background: var(--vscode-editor-background); + color: var(--vscode-editor-foreground); + } + + pre { + margin-top: 4px; + margin-bottom: 4px; } } diff --git a/yarn.lock b/yarn.lock index 00181e9..3f344f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,11 +92,6 @@ stack-trace "^0.0.10" ws "^7.0.0" -"@hediet/semantic-json@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@hediet/semantic-json/-/semantic-json-0.1.0.tgz#ca3831778f1e59e555d0112decd5fe598437fdd3" - integrity sha512-lNdXabUmyxXnysXadTKyiKK9ik5L8b9SXu/QshgvE271QObPtp2Ubmh+rLNNCEAO65DE2VAxcesWmRKYfaQSJg== - "@hediet/std@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.6.0.tgz#f7d2ffc1180ecef6ff272d0d6780f0c0d9337e4e"