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

Poc UI block #2028

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
239add4
Better MicroPython Terminal (#2014)
WebReflection Apr 4, 2024
1fb6cdd
Forgot to update current npm version (#2015)
WebReflection Apr 4, 2024
2f1b764
Provide input("") and sync output to MicroPython (#2016)
WebReflection Apr 4, 2024
65954a6
Allow MicroPython input("...") to work beside the code.interact() (#2…
WebReflection Apr 4, 2024
6d45728
Bump dorny/test-reporter from 1.8.0 to 1.9.0 in the github-actions gr…
dependabot[bot] Apr 8, 2024
6ee8217
Updated dev-dependencies w/ ESLint 9 (#2021)
WebReflection Apr 9, 2024
2d5cf09
Fix MicroPython badly handling unicode chars (#2018)
WebReflection Apr 9, 2024
d7d2dfb
[pre-commit.ci] pre-commit autoupdate (#2012)
pre-commit-ci[bot] Apr 10, 2024
44cd627
PyTerminal .process(code) utility (#2026)
WebReflection Apr 12, 2024
c653296
Added @xterm/addon-web-links to the terminal (#2027)
WebReflection Apr 12, 2024
6ce23a0
add JSProperty to pydom so it can be used to create descriptors mappi…
fpliger Jan 30, 2024
4453132
add pyweb.ui
fpliger Jan 30, 2024
df8793e
fix pyweb imports
fpliger Jan 31, 2024
02f5b4f
fix link and a elements and add a script element
fpliger Jan 31, 2024
d5ee5e9
fix imports and add initialization to load resources to shoelace module
fpliger Jan 31, 2024
b05297a
new pyweb.ui test folder
fpliger Jan 31, 2024
a03f77f
remove comments
fpliger Jan 31, 2024
954fb90
add Icon to shoelace components
fpliger Jan 31, 2024
7ce315d
use html property rather than accessing _js directly
fpliger Jan 31, 2024
647cf89
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 31, 2024
5ab48c8
add markdown suppport
fpliger Jan 31, 2024
3504840
move examples section out of stdlib pyweb to examples.py in the demo …
fpliger Jan 31, 2024
f236580
simplify demo code
fpliger Jan 31, 2024
29540e7
improve docstrings
fpliger Jan 31, 2024
17f0ae4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 31, 2024
de0b336
precommit fixes
fpliger Jan 31, 2024
03253f5
simplify code for main page
fpliger Jan 31, 2024
18c1168
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 31, 2024
4effe4f
add load_resources to markdown
fpliger Jan 31, 2024
94f1ccd
add showlace extra style dynamically
fpliger Feb 1, 2024
d50028c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 31, 2024
7e7be66
precommit
fpliger Feb 1, 2024
96d6efd
add gallery files
fpliger Feb 1, 2024
d9d78ed
add global attributes and dynamic property assignment
fpliger Feb 1, 2024
6844c50
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 1, 2024
9e293ae
fix type that lead using the the JSDescriptor directly instead of the…
fpliger Feb 1, 2024
5775ff2
add missing marked dependency
fpliger Feb 1, 2024
d84335e
refactor gallary to simplify codebase
fpliger Feb 1, 2024
f2cdf18
Rebased and updated dependencies
WebReflection Apr 15, 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
2 changes: 1 addition & 1 deletion .github/workflows/test_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
report:
runs-on: ubuntu-latest-8core
steps:
- uses: dorny/test-reporter@v1.8.0
- uses: dorny/test-reporter@v1.9.0
with:
artifact: test_results
name: Test reports
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ repos:
- id: trailing-whitespace

- repo: https://github.com/psf/black
rev: 24.1.1
rev: 24.3.0
hooks:
- id: black
exclude: pyscript\.core/src/stdlib/pyscript/__init__\.py
Expand Down
427 changes: 200 additions & 227 deletions pyscript.core/package-lock.json

Large diffs are not rendered by default.

19 changes: 10 additions & 9 deletions pyscript.core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pyscript/core",
"version": "0.4.12",
"version": "0.4.21",
"type": "module",
"description": "PyScript",
"module": "./index.js",
Expand All @@ -20,7 +20,7 @@
},
"scripts": {
"server": "npx static-handler --coi .",
"build": "npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
"build": "export ESLINT_USE_FLAT_CONFIG=false; npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy",
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
"build:plugins": "node rollup/plugins.cjs",
"build:stdlib": "node rollup/stdlib.cjs",
Expand All @@ -42,7 +42,7 @@
"dependencies": {
"@ungap/with-resolvers": "^0.1.0",
"basic-devtools": "^0.1.6",
"polyscript": "^0.12.1",
"polyscript": "^0.12.3",
"sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1",
"type-checked-collections": "^0.1.7"
Expand All @@ -52,21 +52,22 @@
"@codemirror/lang-python": "^6.1.5",
"@codemirror/language": "^6.10.1",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.26.1",
"@playwright/test": "^1.42.1",
"@codemirror/view": "^6.26.3",
"@playwright/test": "^1.43.1",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@webreflection/toml-j0.4": "^1.1.3",
"@xterm/addon-fit": "^0.9.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"chokidar": "^3.6.0",
"codemirror": "^6.0.1",
"eslint": "^8.57.0",
"rollup": "^4.13.2",
"eslint": "^9.0.0",
"rollup": "^4.14.3",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0",
"static-handler": "^0.4.3",
"typescript": "^5.4.3",
"typescript": "^5.4.5",
"xterm": "^5.3.0",
"xterm-readline": "^1.1.1"
},
Expand Down
3 changes: 3 additions & 0 deletions pyscript.core/rollup/3rd-party.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ const modules = {
"xterm_addon-fit.js": fetch(`${CDN}/@xterm/addon-fit/+esm`).then((b) =>
b.text(),
),
"xterm_addon-web-links.js": fetch(
`${CDN}/@xterm/addon-web-links/+esm`,
).then((b) => b.text()),
"xterm.css": fetch(`${CDN}/xterm@${v("xterm")}/css/xterm.min.css`).then(
(b) => b.text(),
),
Expand Down
4 changes: 2 additions & 2 deletions pyscript.core/src/plugins/py-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const hooks = {
codeBeforeRun: () => stdlib,
// works on both Pyodide and MicroPython
onReady: ({ runAsync, io }, { sync }) => {
io.stdout = (line) => sync.write(line);
io.stderr = (line) => sync.writeErr(line);
io.stdout = io.buffered(sync.write);
io.stderr = io.buffered(sync.writeErr);
sync.revoke();
sync.runAsync = runAsync;
},
Expand Down
194 changes: 119 additions & 75 deletions pyscript.core/src/plugins/py-terminal.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// PyScript py-terminal plugin
import { TYPES, hooks } from "../core.js";
import { notify } from "./error.js";
import { customObserver, defineProperty } from "polyscript/exports";
import { customObserver, defineProperties } from "polyscript/exports";

// will contain all valid selectors
const SELECTORS = [];
Expand Down Expand Up @@ -32,87 +32,117 @@ const workerReady = ({ interpreter, io, run, type }, { sync }) => {
"from polyscript import currentScript as _; __terminal__ = _.terminal; del _",
);

// This part is shared among both Pyodide and MicroPython
io.stderr = (error) => {
sync.pyterminal_write(`${error.message || error}\n`);
let data = "";
const { pyterminal_read, pyterminal_write } = sync;
const decoder = new TextDecoder();
const generic = {
isatty: false,
write(buffer) {
data = decoder.decode(buffer);
pyterminal_write(data);
return buffer.length;
},
};

const isMicroPython = type === "mpy";
// This part works already in both Pyodide and MicroPython
io.stderr = (error) => {
pyterminal_write(String(error.message || error));
};

// MicroPython has no code or code.interact()
// This part patches it in a way that simulate
// This part patches it in a way that simulates
// the code.interact() module in Pyodide.
if (isMicroPython) {
const encoder = new TextEncoder();
const processData = () => {
if (data.length) {
for (
let i = 0, b = encoder.encode(`${data}\r`);
i < b.length;
i++
) {
const code = interpreter.replProcessChar(b[i]);
if (code) {
throw new Error(
`replProcessChar failed with code ${code}`,
);
if (type === "mpy") {
// monkey patch global input otherwise broken in MicroPython
interpreter.registerJsModule("_pyscript_input", {
input: pyterminal_read,
});
run("from _pyscript_input import input");

// this is needed to avoid truncated unicode in MicroPython
// the reason is that `linebuffer` false just send one byte
// per time and readline here doesn't like it much.
// MicroPython also has issues with code-points and
// replProcessChar(byte) but that function accepts only
// one byte per time so ... we have an issue!
// @see https://github.com/pyscript/pyscript/pull/2018
// @see https://github.com/WebReflection/buffer-points
const bufferPoints = (stdio) => {
const bytes = [];
let needed = 0;
return (buffer) => {
let written = 0;
for (const byte of buffer) {
bytes.push(byte);
// @see https://encoding.spec.whatwg.org/#utf-8-bytes-needed
if (needed) needed--;
else if (0xc2 <= byte && byte <= 0xdf) needed = 1;
else if (0xe0 <= byte && byte <= 0xef) needed = 2;
else if (0xf0 <= byte && byte <= 0xf4) needed = 3;
if (!needed) {
written += bytes.length;
stdio(new Uint8Array(bytes.splice(0)));
}
}
}
data = ">>> ";
data = io.stdin();
processData();
};
interpreter.setStderr = Object; // as no-op
interpreter.setStdout = ({ write }) => {
io.stdout = (str) => {
// avoid duplicated outcome due i/o + readline
const ignore = str.startsWith(`>>> ${data}`);
return ignore ? 0 : write(`${str}\n`);
return written;
};
};
interpreter.setStdin = ({ stdin }) => {
io.stdin = stdin;
};

io.stdout = bufferPoints(generic.write);

// tiny shim of the code module with only interact
// to bootstrap a REPL like environment
interpreter.registerJsModule("code", {
interact() {
let input = "";
let length = 1;

const encoder = new TextEncoder();
const acc = [];
const handlePoints = bufferPoints((buffer) => {
acc.push(...buffer);
pyterminal_write(decoder.decode(buffer));
});

// avoid duplicating the output produced by the input
io.stdout = (buffer) =>
length++ > input.length ? handlePoints(buffer) : 0;

interpreter.replInit();
data = "";
processData();

// loop forever waiting for user inputs
(function repl() {
const out = decoder.decode(new Uint8Array(acc.splice(0)));
// print in current line only the last line produced by the REPL
const data = `${pyterminal_read(out.split("\n").at(-1))}\r`;
length = 0;
input = encoder.encode(data);
for (const c of input) interpreter.replProcessChar(c);
repl();
})();
},
});
} else {
interpreter.setStdout(generic);
interpreter.setStderr(generic);
interpreter.setStdin({
isatty: false,
stdin: () => pyterminal_read(data),
});
}

// This part is inevitably duplicated as external scope
// can't be reached by workers out of the box.
// The detail is that here we use sync though, not readline.
const decoder = new TextDecoder();
let data = "";
const generic = {
isatty: true,
write(buffer) {
data = isMicroPython ? buffer : decoder.decode(buffer);
sync.pyterminal_write(data);
return buffer.length;
},
};
interpreter.setStdout(generic);
interpreter.setStderr(generic);
interpreter.setStdin({
isatty: true,
stdin: () => sync.pyterminal_read(data),
});
};

const pyTerminal = async (element) => {
// lazy load these only when a valid terminal is found
const [{ Terminal }, { Readline }, { FitAddon }] = await Promise.all([
import(/* webpackIgnore: true */ "../3rd-party/xterm.js"),
import(/* webpackIgnore: true */ "../3rd-party/xterm-readline.js"),
import(/* webpackIgnore: true */ "../3rd-party/xterm_addon-fit.js"),
]);
const [{ Terminal }, { Readline }, { FitAddon }, { WebLinksAddon }] =
await Promise.all([
import(/* webpackIgnore: true */ "../3rd-party/xterm.js"),
import(/* webpackIgnore: true */ "../3rd-party/xterm-readline.js"),
import(/* webpackIgnore: true */ "../3rd-party/xterm_addon-fit.js"),
import(
/* webpackIgnore: true */ "../3rd-party/xterm_addon-web-links.js"
),
]);

const readline = new Readline();

Expand Down Expand Up @@ -141,10 +171,29 @@ const pyTerminal = async (element) => {
const fitAddon = new FitAddon();
terminal.loadAddon(fitAddon);
terminal.loadAddon(readline);
terminal.loadAddon(new WebLinksAddon());
terminal.open(target);
fitAddon.fit();
terminal.focus();
defineProperty(element, "terminal", { value: terminal });
defineProperties(element, {
terminal: { value: terminal },
process: {
value: async (code) => {
// this loop is the only way I could find to actually simulate
// the user input char after char in a way that works in both
// MicroPython and Pyodide
for (const line of code.split(/(?:\r|\n|\r\n)/)) {
terminal.paste(`${line}\n`);
do {
await new Promise((resolve) =>
setTimeout(resolve, 0),
);
} while (!readline.activeRead?.resolve);
readline.activeRead.resolve(line);
}
},
},
});
return terminal;
};

Expand Down Expand Up @@ -192,36 +241,31 @@ const pyTerminal = async (element) => {
delete globalThis.__py_terminal__;

io.stderr = (error) => {
readline.write(`${error.message || error}\n`);
readline.write(String(error.message || error));
};

const isMicroPython = type === "mpy";

if (isMicroPython) {
interpreter.setStderr = Object; // as no-op
if (type === "mpy") {
interpreter.setStdin = Object; // as no-op
interpreter.setStderr = Object; // as no-op
interpreter.setStdout = ({ write }) => {
io.stdout = (str) => write(`${str}\n`);
io.stdout = write;
};
}

// This part is inevitably duplicated as external scope
// can't be reached by workers out of the box.
// The detail is that here we use readline here, not sync.
const decoder = new TextDecoder();
let data = "";
const decoder = new TextDecoder();
const generic = {
isatty: true,
isatty: false,
write(buffer) {
data = isMicroPython ? buffer : decoder.decode(buffer);
data = decoder.decode(buffer);
readline.write(data);
return buffer.length;
},
};
interpreter.setStdout(generic);
interpreter.setStderr(generic);
interpreter.setStdin({
isatty: true,
isatty: false,
stdin: () => readline.read(data),
});
});
Expand Down
1 change: 1 addition & 0 deletions pyscript.core/src/stdlib/pyscript/magic_js.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import js as globalThis
from polyscript import js_modules

from pyscript.util import NotSupported

RUNNING_IN_WORKER = not hasattr(globalThis, "document")
Expand Down
2 changes: 2 additions & 0 deletions pyscript.core/src/stdlib/pyweb/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .pydom import JSProperty
from .pydom import dom as pydom
from .pydom import js_property
1 change: 1 addition & 0 deletions pyscript.core/src/stdlib/pyweb/media.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pyodide.ffi import to_js

from pyscript import window


Expand Down
Loading