Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add json tracer as debug option #536

Merged
merged 55 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
7cad7e4
Run trace for basic examples
jorg-vr Oct 6, 2023
f914c38
Simplify io fix
jorg-vr Oct 6, 2023
2989e3a
Update debugger usage to more simplified version
jorg-vr Oct 6, 2023
63bac31
Remove unused change
jorg-vr Oct 6, 2023
9632591
Get traceback in js
jorg-vr Oct 9, 2023
aa3d72b
Merge branch 'main' into feat/add-debug-mode
jorg-vr Dec 8, 2023
10b5821
Add frontend to papyros
jorg-vr Dec 11, 2023
94274a2
Improve import
jorg-vr Dec 11, 2023
9f0041f
Update versions
jorg-vr Dec 12, 2023
45dd431
Use papyros structure
jorg-vr Dec 12, 2023
9249f27
Add Line indicator
jorg-vr Dec 12, 2023
8fcfd40
Add online tracing
jorg-vr Dec 13, 2023
85994d9
Update viewer
jorg-vr Dec 13, 2023
385686b
Remove logging
jorg-vr Dec 13, 2023
e6af416
Highlight used output
jorg-vr Dec 13, 2023
426d182
Highlight input
jorg-vr Dec 13, 2023
1d47765
Update to non beta versions
jorg-vr Dec 13, 2023
7e3cdc2
Fix imported modules
jorg-vr Dec 14, 2023
5014578
Allow scrolling on page
jorg-vr Dec 15, 2023
3daaf05
Update tracecomponent version
jorg-vr Dec 15, 2023
839cfc9
Remove arrow when starting a normal run
jorg-vr Dec 15, 2023
072679b
Fix linting
jorg-vr Dec 15, 2023
c3ab7f4
Publish beta
jorg-vr Dec 15, 2023
ebeb912
Update trace-component version
jorg-vr Dec 18, 2023
45bb956
Update trace-component version
jorg-vr Dec 18, 2023
b867469
Update trace-component version
jorg-vr Dec 18, 2023
a9b5f88
Speedup by buffering frames
jorg-vr Dec 18, 2023
1870ea3
Publish changes
jorg-vr Dec 18, 2023
ae8a507
Update trace-component
jorg-vr Dec 18, 2023
4fc9820
Update trace-component
jorg-vr Dec 18, 2023
31f6b39
Update tracecomponent
jorg-vr Dec 18, 2023
950d84e
Add help text
jorg-vr Dec 19, 2023
27e1bf4
Only show stopbutton while running
jorg-vr Dec 19, 2023
6139fe0
Update trace component
jorg-vr Dec 20, 2023
8c0fc6a
Update tracer
jorg-vr Dec 20, 2023
3957b0c
Publish package
jorg-vr Dec 20, 2023
d96fc81
Remove important
jorg-vr Dec 22, 2023
4ba097b
Fix button layout
jorg-vr Dec 22, 2023
7a37c5e
Republish after build
jorg-vr Dec 22, 2023
c432fb1
Dont inline reverse
jorg-vr Dec 22, 2023
95fd181
Rename traceviewer to debugger
jorg-vr Dec 22, 2023
5f70a6d
Reintroduce step limit
jorg-vr Dec 22, 2023
02ed03a
Bump version
jorg-vr Dec 22, 2023
930e040
Update tracecompoenent
jorg-vr Dec 22, 2023
ba67c45
Improve text
jorg-vr Jan 10, 2024
751ac01
Allow right alligned button
jorg-vr Jan 11, 2024
c71f455
Translate buttons
jorg-vr Jan 11, 2024
33c4083
Add icons to buttons
jorg-vr Jan 11, 2024
d763a1e
Publish
jorg-vr Jan 11, 2024
bf7d48a
Publish
jorg-vr Jan 11, 2024
f403505
Add debug runmode on first load
jorg-vr Jan 19, 2024
85a5ae9
Only show debug gutter while debugging
jorg-vr Jan 19, 2024
c5e19f0
Publish
jorg-vr Jan 19, 2024
f7052cc
Update trace component
jorg-vr Jan 22, 2024
7cd1616
v2.1.0
jorg-vr Jan 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dodona/papyros",
"version": "2.0.0",
"version": "2.1.0",
"private": false,
"homepage": ".",
"devDependencies": {
Expand Down Expand Up @@ -45,6 +45,7 @@
"@codemirror/state": "^6.3.2",
"@codemirror/theme-one-dark": "^6.1.2",
"@codemirror/view": "^6.22.1",
"@dodona/trace-component": "1.1.6",
"@lezer/common": "^1.1.0",
"comlink": "^4.4.1",
"comsync": "^0.0.9",
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_library.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ cd src/workers/python
python3 build_package.py
cd -
tsc
mv src/workers/python/python_package.tar.gz.load_by_url dist/workers/python
cp src/workers/python/python_package.tar.gz.load_by_url dist/workers/python
npx tailwindcss -i ./src/Papyros.css -o ./dist/Papyros.css
5 changes: 4 additions & 1 deletion src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ async function startPapyros(): Promise<void> {
standAloneOptions: {
parentElementId: "root"
},
darkMode: darkMode
darkMode: darkMode,
traceOptions: {
parentElementId: "trace-root"
}
});
setUpEditor(papyros.codeRunner.editor, LOCAL_STORAGE_KEYS.code);
papyros.codeRunner.editor.focus();
Expand Down
7 changes: 4 additions & 3 deletions src/Backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ export interface WorkerDiagnostic {
message: string;
}

export interface RunMode {
mode: string;
active: boolean;
export enum RunMode {
Run = "run",
Debug = "debug",
Doctest = "doctest"
}

export abstract class Backend<Extras extends SyncExtras = SyncExtras> {
Expand Down
15 changes: 5 additions & 10 deletions src/BackendEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@ export enum BackendEventType {
Sleep = "sleep",
Error = "error",
Interrupt = "interrupt",
Loading = "loading"
Loading = "loading",
Frame = "frame",
FrameChange = "frame-change",
Stop = "stop",
}
/**
* All possible types for ease of iteration
*/
export const BACKEND_EVENT_TYPES = [
BackendEventType.Start, BackendEventType.End,
BackendEventType.Input, BackendEventType.Output,
BackendEventType.Sleep, BackendEventType.Error,
BackendEventType.Interrupt, BackendEventType.Loading
];

/**
* Interface for events used for communication between threads
*/
Expand Down
2 changes: 1 addition & 1 deletion src/BackendManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
if (e.type === BackendEventType.Start) {
BackendManager.halted = false;
}
if (!BackendManager.halted && this.subscriberMap.has(e.type)) {
if ((!BackendManager.halted || e.type === BackendEventType.FrameChange) && this.subscriberMap.has(e.type)) {

Check warning on line 104 in src/BackendManager.ts

View workflow job for this annotation

GitHub Actions / JavaScript

This line has a length of 116. Maximum allowed is 100

Check warning on line 104 in src/BackendManager.ts

View workflow job for this annotation

GitHub Actions / JavaScript

This line has a length of 116. Maximum allowed is 100
this.subscriberMap.get(e.type)!.forEach(cb => cb(e));
}
}
Expand Down
174 changes: 110 additions & 64 deletions src/CodeRunner.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
import { proxy } from "comlink";
import { SyncClient } from "comsync";
import { Backend } from "./Backend";
import { Backend, RunMode } from "./Backend";
import { BackendEvent, BackendEventType } from "./BackendEvent";
import { BackendManager } from "./BackendManager";
import { CodeEditor } from "./editor/CodeEditor";
import {
addPapyrosPrefix,
APPLICATION_STATE_TEXT_ID, CODE_BUTTONS_WRAPPER_ID, DEFAULT_EDITOR_DELAY, RUN_BTN_ID,
STATE_SPINNER_ID, STOP_BTN_ID
APPLICATION_STATE_TEXT_ID,
CODE_BUTTONS_WRAPPER_ID,
DEFAULT_EDITOR_DELAY,
RUN_BUTTONS_WRAPPER_ID,
STATE_SPINNER_ID,
STOP_BTN_ID
} from "./Constants";
import { InputManager, InputManagerRenderOptions, InputMode } from "./InputManager";
import { ProgrammingLanguage } from "./ProgrammingLanguage";
import { renderSpinningCircle } from "./util/HTMLShapes";
import { addListener, downloadResults, getElement, parseData, t } from "./util/Util";
import {
addListener, getElement,
t, downloadResults, parseData
} from "./util/Util";
import {
RenderOptions, renderWithOptions,
renderButton, ButtonOptions, Renderable, appendClasses
appendClasses,
ButtonOptions,
Renderable,
renderButton,
RenderOptions,
renderWithOptions
} from "./util/Rendering";
import { OutputManager } from "./OutputManager";
import { Debugger } from "./Debugger";

const MODE_ICONS: Record<RunMode, string> = {
"debug": "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"18px\" viewBox=\"0 0 24 24\" width=\"18px\" fill=\"currentColor\"><path d=\"M19 7H16.19C15.74 6.2 15.12 5.5 14.37 5L16 3.41L14.59 2L12.42 4.17C11.96 4.06 11.5 4 11 4S10.05 4.06 9.59 4.17L7.41 2L6 3.41L7.62 5C6.87 5.5 6.26 6.21 5.81 7H3V9H5.09C5.03 9.33 5 9.66 5 10V11H3V13H5V14C5 14.34 5.03 14.67 5.09 15H3V17H5.81C7.26 19.5 10.28 20.61 13 19.65V19C13 18.43 13.09 17.86 13.25 17.31C12.59 17.76 11.8 18 11 18C8.79 18 7 16.21 7 14V10C7 7.79 8.79 6 11 6S15 7.79 15 10V14C15 14.19 15 14.39 14.95 14.58C15.54 14.04 16.24 13.62 17 13.35V13H19V11H17V10C17 9.66 16.97 9.33 16.91 9H19V7M13 9V11H9V9H13M13 13V15H9V13H13M17 16V22L22 19L17 16Z\"></path></svg>",

Check warning on line 32 in src/CodeRunner.ts

View workflow job for this annotation

GitHub Actions / JavaScript

This line has a length of 714. Maximum allowed is 100

Check warning on line 32 in src/CodeRunner.ts

View workflow job for this annotation

GitHub Actions / JavaScript

This line has a length of 714. Maximum allowed is 100
"doctest": "<i class=\"mdi mdi-play\"></i>",
"run": "<i class=\"mdi mdi-play\"></i>"
};

interface DynamicButton {
id: string;
Expand All @@ -45,6 +57,7 @@
* RenderOptions for the output field
*/
outputOptions: RenderOptions;
traceOptions: RenderOptions;
}

/**
Expand Down Expand Up @@ -72,6 +85,29 @@
status: "loading" | "loaded" | "failed";

}

/**
* Helper class to avoid code duplication when handling buttons
* It is an ordered array that does not allow duplicate ids
*/
class ButtonArray extends Array<DynamicButton> {
public add(button: ButtonOptions, onClick: () => void): void {
this.remove(button.id);
this.push({
id: button.id,
buttonHTML: renderButton(button),
onClick
});
}

public remove(id: string): void {
const existingIndex = this.findIndex(b => b.id === id);
if (existingIndex !== -1) {
this.splice(existingIndex, 1);
}
}
}

/**
* Helper component to manage and visualize the current RunState
*/
Expand All @@ -92,6 +128,7 @@
* Component to handle output generated by the user's code
*/
public readonly outputManager: OutputManager;
public readonly traceViewer: Debugger;
/**
* The backend that executes the code asynchronously
*/
Expand All @@ -101,9 +138,13 @@
*/
private state: RunState;
/**
* Buttons managed by this component
* Foreign buttons inserted into this component
*/
private userButtons: ButtonArray;
/**
* Internal buttons for different run modes
*/
private buttons: Array<DynamicButton>;
private runButtons: ButtonArray;

/**
* Array of packages that are being installed
Expand Down Expand Up @@ -138,23 +179,14 @@
}, inputMode);
this.outputManager = new OutputManager();
this.backend = Promise.resolve({} as SyncClient<Backend>);
this.buttons = [];
this.userButtons = new ButtonArray();
this.runButtons = new ButtonArray();
this.updateRunButtons([RunMode.Debug]);
this.editor.onChange({
onChange: async code => {
const backend = await this.backend;
const modes = await backend.workerProxy.runModes(code);
modes.forEach(mode => {
const id = addPapyrosPrefix(mode.mode);
if (mode.active) {
this.addButton({
id: id,
buttonText: t(`Papyros.run_modes.${mode.mode}`),
classNames: "_tw-text-white _tw-bg-neutral-bg"
}, () => this.runCode(this.editor.getText(), mode.mode));
} else {
this.removeButton(id);
}
});
this.updateRunButtons(modes);
this.renderButtons();
},
delay: DEFAULT_EDITOR_DELAY
Expand All @@ -167,9 +199,27 @@
e => this.onLoad(e));
BackendManager.subscribe(BackendEventType.Start,
e => this.onStart(e));
BackendManager.subscribe(BackendEventType.Stop, () => this.stop());
this.previousState = RunState.Ready;
this.runStartTime = new Date().getTime();
this.state = RunState.Ready;
this.traceViewer = new Debugger();
}

private updateRunButtons(modes: Array<RunMode>): void {
this.runButtons = new ButtonArray();
this.addRunButton(RunMode.Run, "btn-primary");
modes.forEach(m => this.addRunButton(m));
}

private addRunButton(mode: RunMode, classNames = "btn-secondary"): void {
const id = addPapyrosPrefix(mode)+"-code-btn";
this.runButtons.add({
id: id,
buttonText: t(`Papyros.run_modes.${mode}`),
classNames,
icon: MODE_ICONS[mode]
}, () => this.runCode(this.editor.getText(), mode));
}

/**
Expand Down Expand Up @@ -277,10 +327,8 @@
* @param {string} id Identifier of the button to remove
*/
private removeButton(id: string): void {
const existingIndex = this.buttons.findIndex(b => b.id === id);
if (existingIndex !== -1) {
this.buttons.splice(existingIndex, 1);
}
this.userButtons.remove(id);
this.renderButtons(this.userButtons, CODE_BUTTONS_WRAPPER_ID);
}

/**
Expand All @@ -289,76 +337,74 @@
* @param {function} onClick Listener for click events on the button
*/
public addButton(options: ButtonOptions, onClick: () => void): void {
this.removeButton(options.id);
this.buttons.push({
id: options.id,
buttonHTML: renderButton(options),
onClick: onClick
});
this.userButtons.add(options, onClick);
this.renderButtons(this.userButtons, CODE_BUTTONS_WRAPPER_ID);
}

/**
* Generate a button that the user can click to process code
* Can either run the code or interrupt it if already running
* @return {DynamicButton} A button to interact with the code according to the current state
* @return {DynamicButton} A list of buttons to interact with the code according to the current state

Check warning on line 347 in src/CodeRunner.ts

View workflow job for this annotation

GitHub Actions / JavaScript

This line has a length of 105. Maximum allowed is 100

Check warning on line 347 in src/CodeRunner.ts

View workflow job for this annotation

GitHub Actions / JavaScript

This line has a length of 105. Maximum allowed is 100
*/
private getCodeActionButton(): DynamicButton {
private getCodeActionButtons(): DynamicButton[] {
let buttonOptions: ButtonOptions;
let buttonHandler: () => void;
if ([RunState.Ready, RunState.Loading].includes(this.state)) {
buttonOptions = {
id: RUN_BTN_ID,
buttonText: t("Papyros.run"),
classNames: "_tw-text-white _tw-bg-blue-500"
};
buttonHandler = () => this.runCode(this.editor.getText());
return this.runButtons;
} else {
buttonOptions = {
id: STOP_BTN_ID,
buttonText: t("Papyros.stop"),
classNames: "_tw-text-white _tw-bg-red-500"
classNames: "btn-danger",
icon: "<i class=\"mdi mdi-stop\"></i>"
};
buttonHandler = () => this.stop();

return [{
id: buttonOptions.id,
buttonHTML: renderButton(buttonOptions),
onClick: () => this.stop()
}];
}
appendClasses(buttonOptions, "_tw-min-w-[60px]");
return {
id: buttonOptions.id,
buttonHTML: renderButton(buttonOptions),
onClick: buttonHandler
};
}

/**
* Specific helper method to render only the buttons required by the user
* @param {DynamicButton[]} buttons The buttons to render
* @param {string} id The id of the element to render the buttons in
*/
private renderButtons(): void {
const buttons = [this.getCodeActionButton(), ...this.buttons];
getElement(CODE_BUTTONS_WRAPPER_ID).innerHTML =
buttons.map(b => b.buttonHTML).join("\n");
private renderButtons(buttons: DynamicButton[] | undefined = undefined, id = RUN_BUTTONS_WRAPPER_ID): void {

Check warning on line 374 in src/CodeRunner.ts

View workflow job for this annotation

GitHub Actions / JavaScript

This line has a length of 112. Maximum allowed is 100

Check warning on line 374 in src/CodeRunner.ts

View workflow job for this annotation

GitHub Actions / JavaScript

This line has a length of 112. Maximum allowed is 100
const btns = buttons || this.getCodeActionButtons();
getElement(id).innerHTML =
btns.map(b => b.buttonHTML).join("\n");
// Buttons are freshly added to the DOM, so attach listeners now
buttons.forEach(b => addListener(b.id, b.onClick, "click"));
btns.forEach(b => addListener(b.id, b.onClick, "click"));
}

protected override _render(options: CodeRunnerRenderOptions): HTMLElement {
appendClasses(options.statusPanelOptions,
// eslint-disable-next-line max-len
"_tw-border-solid _tw-border-gray-200 _tw-border-b-2 dark:_tw-border-dark-mode-content");
const rendered = renderWithOptions(options.statusPanelOptions, `
<div class="_tw-grid _tw-grid-cols-2 _tw-items-center _tw-px-1">
<div id="${CODE_BUTTONS_WRAPPER_ID}" class="_tw-col-span-1 _tw-flex _tw-flex-row">
</div>
<div class="_tw-col-span-1 _tw-flex _tw-flex-row-reverse _tw-items-center">
<div id="${APPLICATION_STATE_TEXT_ID}"></div>
<div style="position: relative">
<div style="position: absolute; right: 8px; top: -25px; display: flex">
${renderSpinningCircle(STATE_SPINNER_ID, "_tw-border-gray-200 _tw-border-b-red-500")}
<div id="${APPLICATION_STATE_TEXT_ID}"></div>
</div>
</div>
<div class="_tw-items-center _tw-px-1 _tw-flex _tw-flex-row _tw-justify-between">
<div id="${RUN_BUTTONS_WRAPPER_ID}">
</div>
<div id="${CODE_BUTTONS_WRAPPER_ID}">
</div>
</div>`);
this.setState(this.state);
this.renderButtons(this.userButtons, CODE_BUTTONS_WRAPPER_ID);
this.inputManager.render(options.inputOptions);
this.outputManager.render(options.outputOptions);
this.editor.render(options.codeEditorOptions);
this.editor.setPanel(rendered);
// Set language again to update the placeholder
this.editor.setProgrammingLanguage(this.programmingLanguage);
this.traceViewer.render(options.traceOptions);
return rendered;
}

Expand Down Expand Up @@ -405,9 +451,6 @@
// Was interrupted, End message already published
interrupted = true;
}
this.setState(RunState.Ready, t(
interrupted ? "Papyros.interrupted" : "Papyros.finished",
{ time: (new Date().getTime() - this.runStartTime) / 1000 }));
if (terminated) {
await this.start();
} else if (await backend.workerProxy.hasOverflow()) {
Expand All @@ -421,6 +464,9 @@
);
});
}
this.setState(RunState.Ready, t(
interrupted ? "Papyros.interrupted" : "Papyros.finished",
{ time: (new Date().getTime() - this.runStartTime) / 1000 }));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const PANEL_WRAPPER_ID = addPapyrosPrefix("code-status-panel");
export const STATE_SPINNER_ID = addPapyrosPrefix("state-spinner");
export const APPLICATION_STATE_TEXT_ID = addPapyrosPrefix("application-state-text");
export const CODE_BUTTONS_WRAPPER_ID = addPapyrosPrefix("code-buttons");
export const RUN_BTN_ID = addPapyrosPrefix("run-code-btn");
export const RUN_BUTTONS_WRAPPER_ID = addPapyrosPrefix("run-buttons");
export const STOP_BTN_ID = addPapyrosPrefix("stop-btn");
export const SEND_INPUT_BTN_ID = addPapyrosPrefix("send-input-btn");
export const SWITCH_INPUT_MODE_A_ID = addPapyrosPrefix("switch-input-mode");
Expand Down
Loading
Loading