Skip to content

Commit

Permalink
Merge pull request #12 from Pkcarreno/wip/1
Browse files Browse the repository at this point in the history
Wip/1
  • Loading branch information
Pkcarreno authored Jun 29, 2024
2 parents ebc65c3 + 6b57469 commit 1c6f0b9
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 163 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20.12.1
9 changes: 8 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ export default tseslint.config(
],
'import/prefer-default-export': 'off', // Named export is easier to refactor automatically
'max-params': ['error', 3], // Limit the number of parameters in a function to use object instead
'max-lines-per-function': ['error', 70],
'max-lines-per-function': [
'error',
{
max: 70,
skipBlankLines: true,
skipComments: true,
},
],
'react/destructuring-assignment': 'off', // Vscode doesn't support automatically destructuring, it's a pain to add a new variable
'react/require-default-props': 'off', // Allow non-defined react props as undefined
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"react-hotkeys-hook": "^4.5.0",
"react-resizable-panels": "^2.0.18",
"react-virtuoso": "^4.7.9",
"rfdc": "^1.3.1",
"sonner": "^1.4.2",
"tailwind-merge": "^2.3.0",
"wouter": "^3.2.0",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/assets/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,19 @@
font-synthesis-weight: none;
text-rendering: optimizeLegibility;
}
::-webkit-scrollbar {
@apply size-1.5;
}
::-webkit-scrollbar-thumb {
@apply bg-foreground/20 rounded-full;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-foreground/30;
}
::-webkit-scrollbar-track {
@apply bg-transparent;
}
::-webkit-scrollbar-corner {
@apply bg-transparent;
}
}
1 change: 0 additions & 1 deletion src/features/editor/components/header/action-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { runJs, stopJs } from '@/features/editor/utils/runtime';
import useDebounce from '@/hooks/use-debounce';
import useTimeoutFn from '@/hooks/use-timeout-fn';

// eslint-disable-next-line max-lines-per-function
export const ActionButtons = () => {
const { persist_logs, auto_run, auto_run_timeout } = useSettingsStore();
const { code } = useCodeStore();
Expand Down
4 changes: 2 additions & 2 deletions src/features/editor/components/output/format-output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ interface Props {

export const FormatOutput: React.FC<Props> = ({ value }) => {
if (typeof value === 'string') {
return <span className="whitespace-pre-wrap">{value}</span>;
return <span className="whitespace-pre-wrap break-all">{value}</span>;
}

if (value && Array.isArray(value) && value.length > 0) {
return value.map((subValue, index) => {
if (subValue && typeof subValue === 'string') {
return (
<div key={`string-${index}`} className="w-fit">
<span className="whitespace-pre-wrap">{subValue}</span>
<span className="whitespace-pre-wrap break-all">{subValue}</span>
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/features/editor/components/output/log-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const Value: React.FC<ValueProps> = ({ variant, value, repeats, duration }) => {
<FormatOutput value={value} />
</div>
</div>
<span className=" flex gap-2 text-sm opacity-50">
<span className=" ml-auto flex gap-2 text-sm opacity-50">
{repeats > REPEATS_TOLERANCE && (
<Badge variant="secondary" className="px-2">
{repeats}
Expand Down
172 changes: 14 additions & 158 deletions src/features/editor/utils/eval.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
/* eslint-disable no-async-promise-executor */
import type { QuickJSContext } from 'quickjs-emscripten-core';
import { Arena } from 'quickjs-emscripten-sync';

import { CONSOLE_AVAILABLE_HANDLERS } from '@/features/editor/config/constants';
import type {
consoleAvailableHandlers,
Expand All @@ -11,83 +7,24 @@ import type {
SystemError,
} from '@/features/editor/types';

import { getQuickJS } from './quick-js';
import { disposeQuickJS, executeCode } from './execute-js';
import { rfdc } from './rfdc';

let startTimeRef: number | undefined;

const postMessage: (params: remoteControlerOutsideWorker) => void =
self.postMessage;

let arenaRef: Arena;
let ctxRef: QuickJSContext;

const disposeQuickJS = () => {
console.log('dispose arena and ctx');
if (arenaRef) {
arenaRef.dispose();
}
if (ctxRef) {
ctxRef.dispose();
}
const startTimer = () => {
startTimeRef = Date.now();
};

const getTimeSinceExecutionBegan = () =>
startTimeRef ? Date.now() - startTimeRef : 0;

function serialize(val: unknown): Loggable {
// obj handler
if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
const shallowCopy = { ...val };
const newObj = {} as Record<string, unknown>;
Object.keys(shallowCopy).forEach((k: string) => {
newObj[k] = serialize((shallowCopy as Record<string, unknown>)[k]);
});
return newObj as Loggable;
}

// array handler
if (val !== null && Array.isArray(val)) {
const shallowCopy = [...val];
return shallowCopy.map(serialize) as Loggable;
}

// default
return val as Loggable;
}

const ERROR_PROPERTIES = [
'name',
'message',
'fileName',
'lineNumber',
'columnNumber',
'stack',
];

const poblateErrorObj = (error: Error): Record<string, unknown> => {
const newObj = {} as Record<string, unknown>;
ERROR_PROPERTIES.forEach((p) => {
if (error[p as keyof Error]) {
newObj[p as keyof Error] = error[p as keyof Error];
}
});
return newObj;
};

function serializeError(
error: Error | Record<string, unknown>,
): Record<string, unknown> {
let fixedError = { ...error };
if ('{}' === JSON.stringify(fixedError)) {
fixedError = poblateErrorObj(error as Error);
}
return fixedError;
}
const postMessage: (params: remoteControlerOutsideWorker) => void =
self.postMessage;

const handleLogType = (type: consoleAvailableHandlers) => {
return (...args: unknown[]) => {
const clonedArgs = args.map(serialize);
// console.debug(type, clonedArgs);
const clonedArgs = args.map(rfdc) as Loggable;
postMessage({
command: 'log',
data: {
Expand All @@ -113,13 +50,6 @@ const generateConsoleObj = (): Record<consoleAvailableHandlers, () => void> => {
const consoleHandler: Record<consoleAvailableHandlers, () => void> =
generateConsoleObj();

// TODO: get unsupported console handlers and show error message
// const proxyConsoleHandler = new Proxy(consoleHandler, {
// get: function (target, property) {
// return handleLogType('undefined');
// },
// });

const setTimeoutHandler = <F extends (...args: unknown[]) => unknown>(
callback: F,
timeout?: number,
Expand Down Expand Up @@ -155,86 +85,6 @@ const exposeGlobals = {
URL: UrlHandler,
};

type EvalCode =
| {
status: 'success';
data?: Loggable;
}
| {
status: 'error';
data: SystemError;
};

async function evalUserCode(code: string): Promise<EvalCode> {
return new Promise(async (resolve, reject) => {
try {
console.log('start eval');
const ctx = await getQuickJS().then((deps) => {
return deps.newContext();
});

if (!ctx) reject({ success: false, error: 'no context' });
ctxRef = ctx;

let interruptCycles = 0;
ctx.runtime.setMemoryLimit(1024 * 640);
ctx.runtime.setMaxStackSize(1024 * 320);
ctx.runtime.setInterruptHandler(() => {
return ++interruptCycles > 10;
});

const arena = new Arena(ctx, {
isMarshalable: true,
});
arenaRef = arena;

console.log('expose functions');
arena.expose(exposeGlobals);

console.log('eval the code');
startTimeRef = Date.now();
const result = await arena.evalCode(code);

console.log('execute pending jobs');
arena.executePendingJobs();

let error = undefined;
let success = undefined;

console.log('check output status', result);
if (result) {
if (result.error) {
error = ctx.dump(result.error);
result.error.dispose();
disposeQuickJS();
resolve({ status: 'error', data: error });
}
if (result.value) {
success = ctx.dump(result.value);
result.value.dispose();
disposeQuickJS();
resolve({ status: 'success', data: success });
}
if (result && !result.error && !result.value) {
success = result;
disposeQuickJS();
resolve({ status: 'success', data: success });
}
} else {
disposeQuickJS();
resolve({ status: 'success' });
}

if (error) {
throw error;
}
} catch (error) {
const fixedError = serializeError(error as Error);
reject({ status: 'error', data: fixedError });
}
});
}

self.onmessage = async function ({
data,
}: {
Expand All @@ -245,7 +95,13 @@ self.onmessage = async function ({
try {
const userCode = data.code;
if (userCode.trim().length > 0) {
const evaluationResult = await evalUserCode(userCode.trim());
const evaluationResult = await executeCode({
code: userCode.trim(),
options: {
exposeGlobals: exposeGlobals,
startTimer: startTimer,
},
});

switch (evaluationResult.status) {
case 'success':
Expand Down
Loading

0 comments on commit 1c6f0b9

Please sign in to comment.