diff --git a/src/debugger/debugAdapter.ts b/src/debugger/debugAdapter.ts new file mode 100644 index 000000000..d801ac098 --- /dev/null +++ b/src/debugger/debugAdapter.ts @@ -0,0 +1,38 @@ +import { DebugAdapterTracker, DebugConsole, DebugSession } from "vscode"; +import { MessagingService } from "../service/messagingService"; +import { DEBUG_COMMANDS } from "../view/constants"; + +export class DebugAdapter implements DebugAdapterTracker { + private readonly console: DebugConsole | undefined; + private readonly messagingService: MessagingService; + constructor( + debugSession: DebugSession, + messagingService: MessagingService + ) { + this.console = debugSession.configuration.console; + this.messagingService = messagingService; + } + onWillStartSession() { + // To Implement + } + onWillReceiveMessage(message: any): void { + if (message.command) { + // Only send pertinent debug messages + switch (message.command) { + case DEBUG_COMMANDS.CONTINUE: + this.messagingService.sendStartMessage(); + break; + case DEBUG_COMMANDS.STACK_TRACE: + this.messagingService.sendPauseMessage(); + } + } + } + // A debugger error should unlock the webview + onError() { + this.messagingService.sendStartMessage(); + } + // Device is always running when exiting debugging mode + onExit() { + this.messagingService.sendStartMessage(); + } +} diff --git a/src/debugger/debugAdapterFactory.ts b/src/debugger/debugAdapterFactory.ts new file mode 100644 index 000000000..35e2ee285 --- /dev/null +++ b/src/debugger/debugAdapterFactory.ts @@ -0,0 +1,25 @@ +import { + DebugAdapterTracker, + DebugAdapterTrackerFactory, + DebugSession, + ProviderResult, +} from "vscode"; +import { MessagingService } from "../service/messagingService"; +import { DebugAdapter } from "./debugAdapter"; + +export class DebugAdapterFactory implements DebugAdapterTrackerFactory { + private debugSession: DebugSession; + private messagingService: MessagingService; + constructor( + debugSession: DebugSession, + messagingService: MessagingService + ) { + this.debugSession = debugSession; + this.messagingService = messagingService; + } + public createDebugAdapterTracker( + session: DebugSession + ): ProviderResult { + return new DebugAdapter(session, this.messagingService); + } +} diff --git a/src/debuggerCommunicationServer.ts b/src/debuggerCommunicationServer.ts index 1d73b8c29..20627b260 100644 --- a/src/debuggerCommunicationServer.ts +++ b/src/debuggerCommunicationServer.ts @@ -26,7 +26,7 @@ export class DebuggerCommunicationServer { private simulatorWebview: WebviewPanel | undefined; private currentActiveDevice; private isPendingResponse = false; - private pendingCallbacks: Array = []; + private pendingCallbacks: Function[] = []; constructor( webviewPanel: WebviewPanel | undefined, diff --git a/src/extension.ts b/src/extension.ts index 0f221bad2..8f818c978 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -16,9 +16,11 @@ import { TelemetryEventName, } from "./constants"; import { CPXWorkspace } from "./cpxWorkspace"; +import { DebugAdapterFactory } from "./debugger/debugAdapterFactory"; import { DebuggerCommunicationServer } from "./debuggerCommunicationServer"; import * as utils from "./extension_utils/utils"; import { SerialMonitor } from "./serialMonitor"; +import { MessagingService } from "./service/messagingService"; import { SimulatorDebugConfigurationProvider } from "./simulatorDebugConfigurationProvider"; import TelemetryAI from "./telemetry/telemetryAI"; import { UsbDetector } from "./usbDetector"; @@ -35,6 +37,7 @@ let debuggerCommunicationHandler: DebuggerCommunicationServer; let firstTimeClosed: boolean = true; let shouldShowInvalidFileNamePopup: boolean = true; let shouldShowRunCodePopup: boolean = true; +const messagingService = new MessagingService(); let currentActiveDevice: string = DEFAULT_DEVICE; @@ -121,6 +124,7 @@ export async function activate(context: vscode.ExtensionContext) { const openWebview = () => { if (currentPanel) { + messagingService.setWebview(currentPanel.webview); currentPanel.reveal(vscode.ViewColumn.Beside); } else { currentPanel = vscode.window.createWebviewPanel( @@ -142,6 +146,7 @@ export async function activate(context: vscode.ExtensionContext) { ); currentPanel.webview.html = getWebviewContent(context); + messagingService.setWebview(currentPanel.webview); if (messageListener !== undefined) { messageListener.dispose(); @@ -914,6 +919,14 @@ export async function activate(context: vscode.ExtensionContext) { utils.getPathToScript(context, "out/", "debug_user_code.py") ); + const debugAdapterFactory = new DebugAdapterFactory( + vscode.debug.activeDebugSession, + messagingService + ); + vscode.debug.registerDebugAdapterTrackerFactory( + "python", + debugAdapterFactory + ); // On Debug Session Start: Init comunication const debugSessionsStarted = vscode.debug.onDidStartDebugSession(() => { if (simulatorDebugConfiguration.deviceSimulatorExpressDebug) { diff --git a/src/service/messagingService.ts b/src/service/messagingService.ts new file mode 100644 index 000000000..9e37f20a8 --- /dev/null +++ b/src/service/messagingService.ts @@ -0,0 +1,26 @@ +import { Webview } from "vscode"; +import { VSCODE_MESSAGES_TO_WEBVIEW } from "../view/constants"; +export class MessagingService { + private currentWebviewTarget: Webview | undefined; + + public setWebview(webview: Webview) { + this.currentWebviewTarget = webview; + } + + // Send a message to webview if it exists + public sendMessageToWebview(debugCommand: string, state: Object) { + if (this.currentWebviewTarget) { + this.currentWebviewTarget.postMessage({ command: debugCommand }); + } + } + public sendStartMessage() { + this.currentWebviewTarget.postMessage({ + command: VSCODE_MESSAGES_TO_WEBVIEW.RUN_DEVICE, + }); + } + public sendPauseMessage() { + this.currentWebviewTarget.postMessage({ + command: VSCODE_MESSAGES_TO_WEBVIEW.PAUSE_DEVICE, + }); + } +} diff --git a/src/view/App.tsx b/src/view/App.tsx index b0d26112d..eb9da7b02 100644 --- a/src/view/App.tsx +++ b/src/view/App.tsx @@ -3,15 +3,22 @@ import * as React from "react"; import "./App.css"; -import { DEVICE_LIST_KEY, VSCODE_MESSAGES_TO_WEBVIEW } from "./constants"; +import { + DEVICE_LIST_KEY, + VIEW_STATE, + VSCODE_MESSAGES_TO_WEBVIEW, +} from "./constants"; import { Device } from "./container/device/Device"; +import { ViewStateContext } from "./context"; interface IState { currentDevice: string; + viewState: VIEW_STATE; } const defaultState = { currentDevice: DEVICE_LIST_KEY.CPX, + viewState: VIEW_STATE.RUNNING, }; class App extends React.Component<{}, IState> { @@ -39,7 +46,11 @@ class App extends React.Component<{}, IState> { return (
- + + +
); @@ -47,12 +58,19 @@ class App extends React.Component<{}, IState> { handleMessage = (event: any): void => { const message = event.data; - console.log(JSON.stringify(message)); - if ( - message.command === VSCODE_MESSAGES_TO_WEBVIEW.SET_DEVICE && - message.active_device !== this.state.currentDevice - ) { - this.setState({ currentDevice: message.active_device }); + + switch (message.command) { + case VSCODE_MESSAGES_TO_WEBVIEW.SET_DEVICE: + if (message.active_device !== this.state.currentDevice) { + this.setState({ currentDevice: message.active_device }); + } + break; + case VSCODE_MESSAGES_TO_WEBVIEW.RUN_DEVICE: + this.setState({ viewState: VIEW_STATE.RUNNING }); + break; + case VSCODE_MESSAGES_TO_WEBVIEW.PAUSE_DEVICE: + this.setState({ viewState: VIEW_STATE.PAUSE }); + break; } }; } diff --git a/src/view/components/cpx/CpxSimulator.tsx b/src/view/components/cpx/CpxSimulator.tsx index 31bc067a1..673dd6857 100644 --- a/src/view/components/cpx/CpxSimulator.tsx +++ b/src/view/components/cpx/CpxSimulator.tsx @@ -117,10 +117,6 @@ class Simulator extends React.Component<{}, IState> { running_file: message.state.running_file, }); break; - default: - console.log("Invalid message received from the extension."); - this.setState({ ...this.state, cpx: DEFAULT_CPX_STATE }); - break; } }; diff --git a/src/view/components/microbit/MicrobitImage.tsx b/src/view/components/microbit/MicrobitImage.tsx index df62aa509..d06f4d0e4 100644 --- a/src/view/components/microbit/MicrobitImage.tsx +++ b/src/view/components/microbit/MicrobitImage.tsx @@ -2,8 +2,10 @@ // Licensed under the MIT license. import * as React from "react"; +import { VIEW_STATE } from "../../constants"; +import { ViewStateContext } from "../../context"; import "../../styles/Microbit.css"; -import { MicrobitSvg } from "./Microbit_svg"; +import { IRefObject, MicrobitSvg } from "./Microbit_svg"; interface EventTriggers { onMouseUp: (event: Event, buttonKey: string) => void; @@ -15,6 +17,11 @@ interface IProps { leds: number[][]; } +const BUTTON_CLASSNAME = { + ACTIVE: "sim-button-outer", + DEACTIVATED: "sim-button-deactivated", +}; + // Displays the SVG and call necessary svg modification. export class MicrobitImage extends React.Component { private svgRef: React.RefObject = React.createRef(); @@ -31,17 +38,28 @@ export class MicrobitImage extends React.Component { componentDidUpdate() { if (this.svgRef.current) { updateAllLeds(this.props.leds, this.svgRef.current.getLeds()); + if (this.context === VIEW_STATE.PAUSE) { + disableAllButtons(this.svgRef.current.getButtons()); + } else if (this.context === VIEW_STATE.RUNNING) { + setupAllButtons( + this.props.eventTriggers, + this.svgRef.current.getButtons() + ); + } } } render() { return ; } } + +MicrobitImage.contextType = ViewStateContext; const setupButton = ( - buttonElement: HTMLElement, + buttonElement: SVGRectElement, eventTriggers: EventTriggers, key: string ) => { + buttonElement.setAttribute("class", BUTTON_CLASSNAME.ACTIVE); buttonElement.onmousedown = e => { eventTriggers.onMouseDown(e, key); }; @@ -52,13 +70,27 @@ const setupButton = ( eventTriggers.onMouseLeave(e, key); }; }; -const setupAllButtons = (eventTriggers: EventTriggers, buttonRefs: Object) => { +const setupAllButtons = ( + eventTriggers: EventTriggers, + buttonRefs: IRefObject +) => { for (const [key, ref] of Object.entries(buttonRefs)) { if (ref.current) { setupButton(ref.current, eventTriggers, key); } } }; +const disableAllButtons = (buttonRefs: IRefObject) => { + for (const [, ref] of Object.entries(buttonRefs)) { + if (ref.current) { + // to implement + ref.current.onmousedown = null; + ref.current.onmouseup = null; + ref.current.onmouseleave = null; + ref.current.setAttribute("class", BUTTON_CLASSNAME.DEACTIVATED); + } + } +}; const updateAllLeds = ( leds: number[][], ledRefs: Array>> diff --git a/src/view/components/microbit/MicrobitSimulator.tsx b/src/view/components/microbit/MicrobitSimulator.tsx index c4728e473..3e0c9e1c0 100644 --- a/src/view/components/microbit/MicrobitSimulator.tsx +++ b/src/view/components/microbit/MicrobitSimulator.tsx @@ -82,9 +82,6 @@ export class MicrobitSimulator extends React.Component { running_file: message.state.running_file, }); break; - default: - console.log("Invalid message received from the extension."); - break; } }; componentDidMount() { diff --git a/src/view/components/microbit/Microbit_svg.tsx b/src/view/components/microbit/Microbit_svg.tsx index 1cb00bfd4..3cfe352e1 100644 --- a/src/view/components/microbit/Microbit_svg.tsx +++ b/src/view/components/microbit/Microbit_svg.tsx @@ -4,7 +4,7 @@ // Adapted from : https://makecode.microbit.org/#editor import * as React from "react"; -interface IRefObject { +export interface IRefObject { [key: string]: React.RefObject; } /* tslint:disable */ diff --git a/src/view/components/toolbar/InputSlider.tsx b/src/view/components/toolbar/InputSlider.tsx index 260be15c9..db917a845 100644 --- a/src/view/components/toolbar/InputSlider.tsx +++ b/src/view/components/toolbar/InputSlider.tsx @@ -2,7 +2,8 @@ // Licensed under the MIT license. import * as React from "react"; -import { WEBVIEW_MESSAGES } from "../../constants"; +import { VIEW_STATE, WEBVIEW_MESSAGES } from "../../constants"; +import { ViewStateContext } from "../../context"; import "../../styles/InputSlider.css"; import { sendMessage } from "../../utils/MessageUtils"; import { ISliderProps } from "../../viewUtils"; @@ -24,20 +25,10 @@ class InputSlider extends React.Component { case "reset-state": this.setState({ value: 0 }); break; - case "set-state": - console.log( - "Setting the state: " + JSON.stringify(message.state) - ); - break; - default: - console.log("Invalid message received from the extension."); - this.setState({ value: 0 }); - break; } }; componentDidMount() { - console.log("Mounted"); window.addEventListener("message", this.handleMessage); } @@ -46,6 +37,7 @@ class InputSlider extends React.Component { window.removeEventListener("message", this.handleMessage); } render() { + const isInputDisabled = this.context === VIEW_STATE.PAUSE; return (
{this.props.axisLabel} @@ -78,6 +70,7 @@ class InputSlider extends React.Component { value={this.state.value} aria-label={`${this.props.type} sensor slider`} defaultValue={this.props.minValue.toLocaleString()} + disabled={isInputDisabled} /> {this.props.minLabel} @@ -131,5 +124,6 @@ class InputSlider extends React.Component { return valueInt; }; } +InputSlider.contextType = ViewStateContext; export default InputSlider; diff --git a/src/view/constants.ts b/src/view/constants.ts index c754441e7..331d77be0 100644 --- a/src/view/constants.ts +++ b/src/view/constants.ts @@ -55,6 +55,12 @@ export enum DEVICE_LIST_KEY { MICROBIT = "micro:bit", } +// Pauses on Debug mode alter the state of the view +export enum VIEW_STATE { + PAUSE = "debug-pause", + RUNNING = "running", +} + // export enum WEBVIEW_MESSAGES { SWITCH_DEVICE = "switch-device", @@ -66,6 +72,12 @@ export enum WEBVIEW_MESSAGES { } export enum VSCODE_MESSAGES_TO_WEBVIEW { SET_DEVICE = "set-device", + PAUSE_DEVICE = "pause-device", + RUN_DEVICE = "run-device", +} +export enum DEBUG_COMMANDS { + STACK_TRACE = "stackTrace", + CONTINUE = "continue", } export default CONSTANTS; diff --git a/src/view/context.ts b/src/view/context.ts new file mode 100644 index 000000000..24fafc556 --- /dev/null +++ b/src/view/context.ts @@ -0,0 +1,6 @@ +import * as React from "react"; +import { VIEW_STATE } from "./constants"; + +// View is running by default + +export const ViewStateContext = React.createContext(VIEW_STATE.RUNNING); diff --git a/src/view/styles/Microbit.css b/src/view/styles/Microbit.css index 8c4f20f2c..251839d5e 100644 --- a/src/view/styles/Microbit.css +++ b/src/view/styles/Microbit.css @@ -22,6 +22,7 @@ svg.sim.grayscale { .sim-button:active { fill: orange; } + .sim-board, .sim-display, sim-button { @@ -146,10 +147,6 @@ sim-button { .sim-pin:focus, .sim-thermometer:focus, .sim-shake:focus, -.sim-light-level-button:focus { - stroke: #4d90fe; - stroke-width: 5px !important; -} .no-drag, .sim-text, .sim-text-pin {