mirror of
https://github.com/pyscript/pyscript.git
synced 2026-04-30 16:00:32 -04:00
PyScript Terminal - the latest kind (#1816)
This commit is contained in:
committed by
GitHub
parent
d9bf5cae12
commit
72f266532b
126
pyscript.core/package-lock.json
generated
126
pyscript.core/package-lock.json
generated
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ const [
|
||||
});
|
||||
|
||||
export {
|
||||
TYPES,
|
||||
exportedPyWorker as PyWorker,
|
||||
exportedHooks as hooks,
|
||||
exportedConfig as config,
|
||||
|
||||
158
pyscript.core/src/plugins/py-terminal.js
Normal file
158
pyscript.core/src/plugins/py-terminal.js
Normal 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();
|
||||
@@ -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")
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
28
pyscript.core/test/py-terminal.html
Normal file
28
pyscript.core/test/py-terminal.html
Normal 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>
|
||||
@@ -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)"
|
||||
|
||||
3
pyscript.core/types/core.d.ts
vendored
3
pyscript.core/types/core.d.ts
vendored
@@ -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 };
|
||||
|
||||
7
pyscript.core/types/plugins.d.ts
vendored
7
pyscript.core/types/plugins.d.ts
vendored
@@ -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;
|
||||
|
||||
2
pyscript.core/types/plugins/py-terminal.d.ts
vendored
Normal file
2
pyscript.core/types/plugins/py-terminal.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const _default: Promise<void>;
|
||||
export default _default;
|
||||
Reference in New Issue
Block a user