PyScript Terminal - the latest kind (#1816)

This commit is contained in:
Andrea Giammarchi
2023-10-31 15:16:15 +01:00
committed by GitHub
parent d9bf5cae12
commit 72f266532b
13 changed files with 282 additions and 227 deletions

View File

@@ -1,17 +1,17 @@
{
"name": "@pyscript/core",
"version": "0.3.0",
"version": "0.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@pyscript/core",
"version": "0.3.0",
"version": "0.3.1",
"license": "APACHE-2.0",
"dependencies": {
"@ungap/with-resolvers": "^0.1.0",
"basic-devtools": "^0.1.6",
"polyscript": "^0.5.1",
"polyscript": "^0.5.6",
"sticky-module": "^0.1.0",
"to-json-callback": "^0.1.1",
"type-checked-collections": "^0.1.7"
@@ -23,7 +23,7 @@
"@webreflection/toml-j0.4": "^1.1.3",
"chokidar": "^3.5.3",
"eslint": "^8.52.0",
"rollup": "^4.1.4",
"rollup": "^4.2.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0",
"static-handler": "^0.4.3",
@@ -306,9 +306,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.1.4.tgz",
"integrity": "sha512-WlzkuFvpKl6CLFdc3V6ESPt7gq5Vrimd2Yv9IzKXdOpgbH4cdDSS1JLiACX8toygihtH5OlxyQzhXOph7Ovlpw==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.2.0.tgz",
"integrity": "sha512-8PlggAxGxavr+pkCNeV1TM2wTb2o+cUWDg9M1cm9nR27Dsn287uZtSLYXoQqQcmq+sYfF7lHfd3sWJJinH9GmA==",
"cpu": [
"arm"
],
@@ -319,9 +319,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.1.4.tgz",
"integrity": "sha512-D1e+ABe56T9Pq2fD+R3ybe1ylCDzu3tY4Qm2Mj24R9wXNCq35+JbFbOpc2yrroO2/tGhTobmEl2Bm5xfE/n8RA==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.2.0.tgz",
"integrity": "sha512-+71T85hbMFrJI+zKQULNmSYBeIhru55PYoF/u75MyeN2FcxE4HSPw20319b+FcZ4lWx2Nx/Ql9tN+hoaD3GH/A==",
"cpu": [
"arm64"
],
@@ -332,9 +332,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.1.4.tgz",
"integrity": "sha512-7vTYrgEiOrjxnjsgdPB+4i7EMxbVp7XXtS+50GJYj695xYTTEMn3HZVEvgtwjOUkAP/Q4HDejm4fIAjLeAfhtg==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.2.0.tgz",
"integrity": "sha512-IIIQLuG43QIElT1JZqUP/zqIdiJl4t9U/boa0GZnQTw9m1X0k3mlBuysbgYXeloLT1RozdL7bgw4lpSaI8GOXw==",
"cpu": [
"arm64"
],
@@ -345,9 +345,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.1.4.tgz",
"integrity": "sha512-eGJVZScKSLZkYjhTAESCtbyTBq9SXeW9+TX36ki5gVhDqJtnQ5k0f9F44jNK5RhAMgIj0Ht9+n6HAgH0gUUyWQ==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.2.0.tgz",
"integrity": "sha512-BXcXvnLaea1Xz900omrGJhxHFJfH9jZ0CpJuVsbjjhpniJ6qiLXz3xA8Lekaa4MuhFcJd4f0r+Ky1G4VFbYhWw==",
"cpu": [
"x64"
],
@@ -358,9 +358,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.1.4.tgz",
"integrity": "sha512-HnigYSEg2hOdX1meROecbk++z1nVJDpEofw9V2oWKqOWzTJlJf1UXVbDE6Hg30CapJxZu5ga4fdAQc/gODDkKg==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.2.0.tgz",
"integrity": "sha512-f4K3MKw9Y4AKi4ANGnmPIglr+S+8tO858YrGVuqAHXxJdVghBmz9CPU9kDpOnGvT4g4vg5uNyIFpOOFvffXyMA==",
"cpu": [
"arm"
],
@@ -371,9 +371,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.1.4.tgz",
"integrity": "sha512-TzJ+N2EoTLWkaClV2CUhBlj6ljXofaYzF/R9HXqQ3JCMnCHQZmQnbnZllw7yTDp0OG5whP4gIPozR4QiX+00MQ==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.2.0.tgz",
"integrity": "sha512-bNsTYQBgp4H7w6cT7FZhesxpcUPahsSIy4NgdZjH1ZwEoZHxi4XKglj+CsSEkhsKi+x6toVvMylhjRKhEMYfnA==",
"cpu": [
"arm64"
],
@@ -384,9 +384,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.1.4.tgz",
"integrity": "sha512-aVPmNMdp6Dlo2tWkAduAD/5TL/NT5uor290YvjvFvCv0Q3L7tVdlD8MOGDL+oRSw5XKXKAsDzHhUOPUNPRHVTQ==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.2.0.tgz",
"integrity": "sha512-Jp1NxBJpGLuxRU2ihrQk4IZ+ia5nffobG6sOFUPW5PMYkF0kQtxEbeDuCa69Xif211vUOcxlOnf5IOEIpTEySA==",
"cpu": [
"arm64"
],
@@ -397,9 +397,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.1.4.tgz",
"integrity": "sha512-77Fb79ayiDad0grvVsz4/OB55wJRyw9Ao+GdOBA9XywtHpuq5iRbVyHToGxWquYWlEf6WHFQQnFEttsAzboyKg==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.2.0.tgz",
"integrity": "sha512-3p3iRtQmv2aXw+vtKNyZMLOQ+LSRsqArXjKAh2Oj9cqwfIRe7OXvdkOzWfZOIp1F/x5KJzVAxGxnniF4cMbnsQ==",
"cpu": [
"x64"
],
@@ -410,9 +410,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.1.4.tgz",
"integrity": "sha512-/t6C6niEQTqmQTVTD9TDwUzxG91Mlk69/v0qodIPUnjjB3wR4UA3klg+orR2SU3Ux2Cgf2pWPL9utK80/1ek8g==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.2.0.tgz",
"integrity": "sha512-atih7IF/reUZe4LBLC5Izd44hth2tfDIG8LaPp4/cQXdHh9jabcZEvIeRPrpDq0i/Uu487Qu5gl5KwyAnWajnw==",
"cpu": [
"x64"
],
@@ -423,9 +423,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.1.4.tgz",
"integrity": "sha512-ZY5BHHrOPkMbCuGWFNpJH0t18D2LU6GMYKGaqaWTQ3CQOL57Fem4zE941/Ek5pIsVt70HyDXssVEFQXlITI5Gg==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.2.0.tgz",
"integrity": "sha512-vYxF3tKJeUE4ceYzpNe2p84RXk/fGK30I8frpRfv/MyPStej/mRlojztkN7Jtd1014HHVeq/tYaMBz/3IxkxZw==",
"cpu": [
"arm64"
],
@@ -436,9 +436,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.1.4.tgz",
"integrity": "sha512-XG2mcRfFrJvYyYaQmvCIvgfkaGinfXrpkBuIbJrTl9SaIQ8HumheWTIwkNz2mktCKwZfXHQNpO7RgXLIGQ7HXA==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.2.0.tgz",
"integrity": "sha512-1LZJ6zpl93SaPQvas618bMFarVwufWTaczH4ESAbFcwiC4OtznA6Ym+hFPyIGaJaGEB8uMWWac0uXGPXOg5FGA==",
"cpu": [
"ia32"
],
@@ -449,9 +449,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.1.4.tgz",
"integrity": "sha512-ANFqWYPwkhIqPmXw8vm0GpBEHiPpqcm99jiiAp71DbCSqLDhrtr019C5vhD0Bw4My+LmMvciZq6IsWHqQpl2ZQ==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.2.0.tgz",
"integrity": "sha512-dgQfFdHCNg08nM5zBmqxqc9vrm0DVzhWotpavbPa0j4//MAOKZEB75yGAfzQE9fUJ+4pvM1239Y4IhL8f6sSog==",
"cpu": [
"x64"
],
@@ -781,13 +781,13 @@
}
},
"node_modules/coincident": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/coincident/-/coincident-0.14.2.tgz",
"integrity": "sha512-Xc/lh56dl/v5GT1R3bEWxiLzF5ZTiXE5Flcd0+qvrBGhZsvDha8bgqhpocrvJmELuerhDO3+EQKDdCzPBPodJQ==",
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/coincident/-/coincident-0.14.3.tgz",
"integrity": "sha512-vd5xP+d5vCCcwTTUxQb3LHRi+dhXnuD+Bgjyf1r1H0IPjfXGDs3z2C4RZJifCJmokqf3Ff9BiFealewTBMTgYw==",
"dependencies": {
"@ungap/structured-clone": "^1.2.0",
"@ungap/with-resolvers": "^0.1.0",
"gc-hook": "^0.2.1"
"gc-hook": "^0.2.3"
},
"optionalDependencies": {
"ws": "^8.14.2"
@@ -2121,15 +2121,15 @@
}
},
"node_modules/polyscript": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.5.1.tgz",
"integrity": "sha512-jMFFWCJjYKcan7RV5UBVNg9IfVSss7wB6l5RIdx28pnBu1WSyA7sApLz7NEVbF14g5a5OjwtlvZCphQEA4CfQQ==",
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.5.6.tgz",
"integrity": "sha512-T1iufSnsq33K5m2vECiVvgDd5zJiSum+eNv3/SUTb38vIxQpDG2W4aVffoIXIgPYe2Bij/aU2xW1P9M2CHUifw==",
"dependencies": {
"@ungap/structured-clone": "^1.2.0",
"@ungap/with-resolvers": "^0.1.0",
"basic-devtools": "^0.1.6",
"codedent": "^0.1.2",
"coincident": "^0.14.2",
"coincident": "^0.14.3",
"html-escaper": "^3.0.3",
"sticky-module": "^0.1.0"
}
@@ -2814,9 +2814,9 @@
}
},
"node_modules/rollup": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.1.4.tgz",
"integrity": "sha512-U8Yk1lQRKqCkDBip/pMYT+IKaN7b7UesK3fLSTuHBoBJacCE+oBqo/dfG/gkUdQNNB2OBmRP98cn2C2bkYZkyw==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.2.0.tgz",
"integrity": "sha512-deaMa9Z+jPVeBD2dKXv+h7EbdKte9++V2potc/ADqvVgEr6DEJ3ia9u0joarjC2lX/ubaCRYz3QVx0TzuVqAJA==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@@ -2826,18 +2826,18 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.1.4",
"@rollup/rollup-android-arm64": "4.1.4",
"@rollup/rollup-darwin-arm64": "4.1.4",
"@rollup/rollup-darwin-x64": "4.1.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.1.4",
"@rollup/rollup-linux-arm64-gnu": "4.1.4",
"@rollup/rollup-linux-arm64-musl": "4.1.4",
"@rollup/rollup-linux-x64-gnu": "4.1.4",
"@rollup/rollup-linux-x64-musl": "4.1.4",
"@rollup/rollup-win32-arm64-msvc": "4.1.4",
"@rollup/rollup-win32-ia32-msvc": "4.1.4",
"@rollup/rollup-win32-x64-msvc": "4.1.4",
"@rollup/rollup-android-arm-eabi": "4.2.0",
"@rollup/rollup-android-arm64": "4.2.0",
"@rollup/rollup-darwin-arm64": "4.2.0",
"@rollup/rollup-darwin-x64": "4.2.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.2.0",
"@rollup/rollup-linux-arm64-gnu": "4.2.0",
"@rollup/rollup-linux-arm64-musl": "4.2.0",
"@rollup/rollup-linux-x64-gnu": "4.2.0",
"@rollup/rollup-linux-x64-musl": "4.2.0",
"@rollup/rollup-win32-arm64-msvc": "4.2.0",
"@rollup/rollup-win32-ia32-msvc": "4.2.0",
"@rollup/rollup-win32-x64-msvc": "4.2.0",
"fsevents": "~2.3.2"
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "@pyscript/core",
"version": "0.3.0",
"version": "0.3.1",
"type": "module",
"description": "PyScript",
"module": "./index.js",
@@ -41,7 +41,7 @@
"dependencies": {
"@ungap/with-resolvers": "^0.1.0",
"basic-devtools": "^0.1.6",
"polyscript": "^0.5.1",
"polyscript": "^0.5.6",
"sticky-module": "^0.1.0",
"to-json-callback": "^0.1.1",
"type-checked-collections": "^0.1.7"
@@ -53,7 +53,7 @@
"@webreflection/toml-j0.4": "^1.1.3",
"chokidar": "^3.5.3",
"eslint": "^8.52.0",
"rollup": "^4.1.4",
"rollup": "^4.2.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0",
"static-handler": "^0.4.3",

View File

@@ -113,7 +113,7 @@ for (const [TYPE] of TYPES) {
value().then(({ notify }) => notify(error.message));
}
} else if (!parsed?.plugins?.includes(`!${key}`)) {
toBeAwaited.push(value());
toBeAwaited.push(value().then(({ default: p }) => p));
}
}

View File

@@ -70,6 +70,7 @@ const [
});
export {
TYPES,
exportedPyWorker as PyWorker,
exportedHooks as hooks,
exportedConfig as config,

View File

@@ -0,0 +1,158 @@
// PyScript py-terminal plugin
import { TYPES, hooks } from "../core.js";
const CDN = "https://cdn.jsdelivr.net/npm/xterm";
const XTERM = "5.3.0";
const XTERM_READLINE = "1.1.1";
const SELECTOR = [...TYPES.keys()]
.map((type) => `script[type="${type}"][terminal],${type}-script[terminal]`)
.join(",");
const pyTerminal = async () => {
const terminals = document.querySelectorAll(SELECTOR);
// no results will look further for runtime nodes
if (!terminals.length) return;
// we currently support only one terminal as in "classic"
if (terminals.length > 1)
console.warn("Unable to satisfy multiple terminals");
// if we arrived this far, let's drop the MutationObserver
mo.disconnect();
const [element] = terminals;
// hopefully to be removed in the near future!
if (element.matches('script[type="mpy"],mpy-script'))
throw new Error("Unsupported terminal");
// import styles once and lazily (only on valid terminal)
if (!document.querySelector(`link[href^="${CDN}"]`)) {
document.head.append(
Object.assign(document.createElement("link"), {
rel: "stylesheet",
href: `${CDN}@${XTERM}/css/xterm.min.css`,
}),
);
}
// lazy load these only when a valid terminal is found
const [{ Terminal }, { Readline }] = await Promise.all([
import(/* webpackIgnore: true */ `${CDN}@${XTERM}/+esm`),
import(
/* webpackIgnore: true */ `${CDN}-readline@${XTERM_READLINE}/+esm`
),
]);
const readline = new Readline();
// common main thread initialization for both worker
// or main case, bootstrapping the terminal on its target
const init = (options) => {
let target = element;
const selector = element.getAttribute("target");
if (selector) {
target =
document.getElementById(selector) ||
document.querySelector(selector);
if (!target) throw new Error(`Unknown target ${selector}`);
} else {
target = document.createElement(`${element.type}-terminal`);
target.style.display = "block";
element.after(target);
}
const terminal = new Terminal({
theme: {
background: "#191A19",
foreground: "#F5F2E7",
},
...options,
});
terminal.loadAddon(readline);
terminal.open(target);
terminal.focus();
};
// branch logic for the worker
if (element.hasAttribute("worker")) {
// when the remote thread onReady triggers:
// setup the interpreter stdout and stderr
const workerReady = ({ interpreter }, { sync }) => {
sync.pyterminal_drop_hooks();
const decoder = new TextDecoder();
const generic = {
isatty: true,
write(buffer) {
sync.pyterminal_write(decoder.decode(buffer));
return buffer.length;
},
};
interpreter.setStdout(generic);
interpreter.setStderr(generic);
};
// run in python code able to replace builtins.input
// using the xworker.sync non blocking prompt
const codeBefore = `
import builtins
from pyscript import sync as _sync
builtins.input = lambda prompt: _sync.pyterminal_read(prompt)
`;
// at the end of the code, make the terminal interactive
const codeAfter = `
import code as _code
_code.interact()
`;
// add a hook on the main thread to setup all sync helpers
// also bootstrapping the XTerm target on main
hooks.main.onWorker.add(function worker(_, xworker) {
hooks.main.onWorker.delete(worker);
init({
disableStdin: false,
cursorBlink: true,
cursorStyle: "block",
});
xworker.sync.pyterminal_read = readline.read.bind(readline);
xworker.sync.pyterminal_write = readline.write.bind(readline);
// allow a worker to drop main thread hooks ASAP
xworker.sync.pyterminal_drop_hooks = () => {
hooks.worker.onReady.delete(workerReady);
hooks.worker.codeBeforeRun.delete(codeBefore);
hooks.worker.codeAfterRun.delete(codeAfter);
};
});
// setup remote thread JS/Python code for whenever the
// worker is ready to become a terminal
hooks.worker.onReady.add(workerReady);
hooks.worker.codeBeforeRun.add(codeBefore);
hooks.worker.codeAfterRun.add(codeAfter);
} else {
// in the main case, just bootstrap XTerm without
// allowing any input as that's not possible / awkward
hooks.main.onReady.add(function main({ io }) {
console.warn("py-terminal is read only on main thread");
hooks.main.onReady.delete(main);
init({
disableStdin: true,
cursorBlink: false,
cursorStyle: "underline",
});
io.stdout = (value) => {
readline.write(`${value}\n`);
};
io.stderr = (error) => {
readline.write(`${error.message || error}\n`);
};
});
}
};
const mo = new MutationObserver(pyTerminal);
mo.observe(document, { childList: true, subtree: true });
// try to check the current document ASAP
export default pyTerminal();

View File

@@ -49,7 +49,9 @@
</head>
<body>
<script type="mpy" worker>
from pyscript import document
print("actual code in worker")
document.documentElement.classList.add('worker')
</script>
<script type="mpy">
print("actual code in main")

View File

@@ -22,8 +22,9 @@
display("Hello", "M-PyScript Main 2", append=False)
</mpy-script>
<mpy-script worker>
from pyscript import display
from pyscript import display, document
display("Hello", "M-PyScript Worker", append=False)
document.documentElement.classList.add('worker')
</mpy-script>
</body>
</html>

View File

@@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test';
test('MicroPython display', async ({ page }) => {
await page.goto('http://localhost:8080/test/mpy.html');
await page.waitForSelector('html.done');
await page.waitForSelector('html.done.worker');
const body = await page.evaluate(() => document.body.innerText);
await expect(body.trim()).toBe([
'M-PyScript Main 1',
@@ -19,7 +19,7 @@ test('MicroPython hooks', async ({ page }) => {
logs.push(text);
});
await page.goto('http://localhost:8080/test/hooks.html');
await page.waitForSelector('html.done');
await page.waitForSelector('html.done.worker');
await expect(logs.join('\n')).toBe([
'main onReady',
'main onBeforeRun',

View File

@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyTerminal</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<style>.xterm { padding: .5rem; }</style>
</head>
<body>
<script type="py">
def greetings(event):
print('hello world')
</script>
<py-script terminal>
import sys
from pyscript import display
display("Hello", "PyScript Next - PyTerminal", append=False)
print("this should go to the terminal")
print("another line")
# this works as expected
print("this goes to stderr", file=sys.stderr)
</py-script>
<button id="my-button" py-click="greetings">Click me</button>
</body>
</html>

View File

@@ -1,17 +1,12 @@
import time
import pytest
from playwright.sync_api import expect
from .support import PyScriptTest, skip_worker
pytest.skip(
reason="FIX LATER: pyscript NEXT doesn't support the Terminal yet",
allow_module_level=True,
)
class TestPyTerminal(PyScriptTest):
@skip_worker("FIXME: the auto worker dance removes terminal")
def test_py_terminal(self):
"""
1. <py-terminal> should redirect stdout and stderr to the DOM
@@ -20,9 +15,7 @@ class TestPyTerminal(PyScriptTest):
"""
self.pyscript_run(
"""
<py-terminal></py-terminal>
<script type="py">
<script type="py" terminal>
import sys
print('hello world')
print('this goes to stderr', file=sys.stderr)
@@ -32,127 +25,32 @@ class TestPyTerminal(PyScriptTest):
)
term = self.page.locator("py-terminal")
term_lines = term.inner_text().splitlines()
assert term_lines == [
"hello world",
"this goes to stderr",
"this goes to stdout",
]
assert self.console.log.lines[-3:] == [
assert term_lines[0:3] == [
"hello world",
"this goes to stderr",
"this goes to stdout",
]
@skip_worker("FIXME: js.document")
def test_two_terminals(self):
"""
Multiple <py-terminal>s can cohexist.
A <py-terminal> receives only output from the moment it is added to
the DOM.
"""
@skip_worker("FIXME: the auto worker dance removes terminal")
def test_button_action(self):
self.pyscript_run(
"""
<py-terminal id="term1"></py-terminal>
<script type="py">
import js
print('one')
term2 = js.document.createElement('py-terminal')
term2.id = 'term2'
js.document.body.append(term2)
print('two')
print('three')
def greetings(event):
print('hello world')
</script>
"""
)
term1 = self.page.locator("#term1")
term2 = self.page.locator("#term2")
term1_lines = term1.inner_text().splitlines()
term2_lines = term2.inner_text().splitlines()
assert term1_lines == ["one", "two", "three"]
assert term2_lines == ["two", "three"]
<script type="py" terminal></script>
def test_auto_attribute(self):
self.pyscript_run(
"""
<py-terminal auto></py-terminal>
<button id="my-button" py-click="print('hello world')">Click me</button>
"""
)
term = self.page.locator("py-terminal")
expect(term).to_be_hidden()
self.page.locator("button").click()
expect(term).to_be_visible()
assert term.inner_text() == "hello world\n"
def test_config_auto(self):
"""
config.terminal == "auto" is the default: a <py-terminal auto> is
automatically added to the page
"""
self.pyscript_run(
"""
<button id="my-button" py-click="print('hello world')">Click me</button>
"""
)
term = self.page.locator("py-terminal")
expect(term).to_be_hidden()
assert "No <py-terminal> found, adding one" in self.console.info.text
#
self.page.locator("button").click()
expect(term).to_be_visible()
assert term.inner_text() == "hello world\n"
def test_config_true(self):
"""
If we set config.terminal == true, a <py-terminal> is automatically added
"""
self.pyscript_run(
"""
<py-config>
terminal = true
</py-config>
<script type="py">
print('hello world')
</script>
"""
)
term = self.page.locator("py-terminal")
expect(term).to_be_visible()
assert term.inner_text() == "hello world\n"
def test_config_false(self):
"""
If we set config.terminal == false, no <py-terminal> is added
"""
self.pyscript_run(
"""
<py-config>
terminal = false
</py-config>
"""
)
term = self.page.locator("py-terminal")
assert term.count() == 0
def test_config_docked(self):
"""
config.docked == "docked" is also the default: a <py-terminal auto docked> is
automatically added to the page
"""
self.pyscript_run(
"""
<button id="my-button" py-click="print('hello world')">Click me</button>
<button id="my-button" py-click="greetings">Click me</button>
"""
)
term = self.page.locator("py-terminal")
self.page.locator("button").click()
expect(term).to_be_visible()
assert term.get_attribute("docked") == ""
last_line = self.page.get_by_text("hello world")
last_line.wait_for()
assert term.inner_text().rstrip() == "hello world"
@skip_worker("FIXME: the auto worker dance removes terminal")
def test_xterm_function(self):
"""Test a few basic behaviors of the xtermjs terminal.
@@ -164,10 +62,7 @@ class TestPyTerminal(PyScriptTest):
"""
self.pyscript_run(
"""
<py-config>
xterm = true
</py-config>
<script type="py">
<script type="py" terminal>
print("\x1b[33mYellow\x1b[0m")
print("\x1b[4mUnderline\x1b[24m")
print("\x1b[1mBold\x1b[22m")
@@ -234,37 +129,3 @@ class TestPyTerminal(PyScriptTest):
"(element) => getComputedStyle(element).getPropertyValue('font-style')"
)
assert font_style == "italic"
def test_xterm_multiple(self):
"""Test whether multiple x-terms on the page all function"""
self.pyscript_run(
"""
<py-config>
xterm = true
</py-config>
<script type="py">
print("\x1b[33mYellow\x1b[0m")
print("done")
</script>
<py-terminal id="a"></py-terminal>
<py-terminal id="b" data-testid="b"></py-terminal>
"""
)
# Wait for "done" to actually appear in the xterm; may be delayed,
# since xtermjs processes its input buffer in chunks
last_line = self.page.get_by_test_id("b").get_by_text("done")
last_line.wait_for()
# Yes, this is not ideal. See note in `test_xterm_function`
time.sleep(1)
rows = self.page.locator("#a .xterm-rows")
# First line should be yellow
first_line = rows.locator("div").nth(0)
first_char = first_line.locator("span").nth(0)
color = first_char.evaluate(
"(element) => getComputedStyle(element).getPropertyValue('color')"
)
assert color == "rgb(196, 160, 0)"

View File

@@ -1,5 +1,6 @@
import TYPES from "./types.js";
declare const exportedPyWorker: any;
declare const exportedHooks: any;
declare const exportedConfig: any;
declare const exportedWhenDefined: any;
export { exportedPyWorker as PyWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
export { TYPES, exportedPyWorker as PyWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };

View File

@@ -1,4 +1,5 @@
declare namespace _default {
function error(): Promise<typeof import("./plugins/error.js")>;
}
declare const _default: {
error: () => Promise<typeof import("./plugins/error.js")>;
"py-terminal": () => Promise<typeof import("./plugins/py-terminal.js")>;
};
export default _default;

View File

@@ -0,0 +1,2 @@
declare const _default: Promise<void>;
export default _default;