Compare commits

...

11 Commits

Author SHA1 Message Date
Andrea Giammarchi
ce923a354f Updated to latest Pyodide + cleaner Polyscript (#2256) 2024-11-27 13:25:37 +01:00
Andrea Giammarchi
7e65836423 Factored out codemirror as chunked lazy based import (#2252)
* Factored out codemirror as chunked lazy based import
2024-11-26 14:17:07 +01:00
Andrea Giammarchi
796373cfa6 Fix #2246 - Override builtins.input to avoid duplicating it (#2254) 2024-11-25 14:17:30 +01:00
Andrea Giammarchi
0a1d3bb678 Fix #2245 - Filter all versions w/ Pyodide frozen cache (#2251) 2024-11-19 11:47:47 +01:00
Andrea Giammarchi
4e43d3e92d Fix #2242 - Improved Xterm.js glyphs handling (#2248)
* Updated dev/dependencies

* Fix #2242 - Improved Xterm.js glyphs handling
2024-11-14 10:28:38 +01:00
Andrea Giammarchi
5acc2afaf3 WIP: Xterm update (#2237)
* WIP: Xterm Update
2024-11-12 10:21:36 +01:00
Nicholas Tollervey
56c64cbee7 Refactor @when and add Event (#2239)
* Add two unit tests for illustrative purposes.

* Radical simplification of @when, more tests and some minor refactoring.
Handle ElementCollections, tests for ElementCollection, make serve for running tests locally.

* Skip flakey Pyodide in worker test (it works 50/50 and appears to be a timing issue).

* Ensure onFOO relates to an underlying FOO event in an Element.

* Minor comment cleanup.

* Add async test for Event listeners.

* Handlers no longer require an event parameter.

* Add tests for async handling via when.

* Docstring cleanup.

* Refactor onFOO to on_FOO.

* Minor typo tidy ups.

* Use correct check for MicroPython.

---------

Co-authored-by: Andrea Giammarchi <andrea.giammarchi@gmail.com>
2024-11-05 13:55:28 +00:00
Andrea Giammarchi
4ff02a24d1 Fix #2240 - Update polyscript to allow targeting /* multiple times (#2241) 2024-11-05 14:20:26 +01:00
Andrea Giammarchi
a5dc94792b Fixed MicroPython error on non-worker terminal (#2238) 2024-10-30 16:08:50 +01:00
Andrea Giammarchi
0db79e0f02 Fix #2065 - Allow usage of index_urls for Pyodide (#2235) 2024-10-30 10:34:45 +01:00
Andrea Giammarchi
283eabdb30 Added pinned lockFileURL test (#2234) 2024-10-29 11:12:21 +01:00
41 changed files with 1243 additions and 688 deletions

View File

@@ -70,6 +70,10 @@ precommit-check:
test:
cd core && npm run test:integration
# Serve the repository with the correct headers.
serve:
npx mini-coi .
# Format the code.
fmt: fmt-py
@echo "Format completed"

View File

@@ -76,7 +76,7 @@ Read the [contributing guide](https://docs.pyscript.net/latest/contributing/)
to learn about our development process, reporting bugs and improvements,
creating issues and asking questions.
Check out the [developing process](https://docs.pyscript.net/latest/developers/)
Check out the [development process](https://docs.pyscript.net/latest/developers/)
documentation for more information on how to setup your development environment.
## Governance

589
core/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@pyscript/core",
"version": "0.6.13",
"version": "0.6.20",
"type": "module",
"description": "PyScript",
"module": "./index.js",
@@ -62,7 +62,7 @@
"@webreflection/idb-map": "^0.3.2",
"add-promise-listener": "^0.1.3",
"basic-devtools": "^0.1.6",
"polyscript": "^0.16.4",
"polyscript": "^0.16.9",
"sabayon": "^0.6.0",
"sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1",
@@ -71,9 +71,9 @@
"devDependencies": {
"@codemirror/commands": "^6.7.1",
"@codemirror/lang-python": "^6.1.6",
"@codemirror/language": "^6.10.3",
"@codemirror/language": "^6.10.4",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.34.1",
"@codemirror/view": "^6.35.0",
"@playwright/test": "1.45.3",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-node-resolve": "^15.3.0",
@@ -81,20 +81,20 @@
"@webreflection/toml-j0.4": "^1.1.3",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"bun": "^1.1.33",
"@xterm/xterm": "^5.5.0",
"bun": "^1.1.37",
"chokidar": "^4.0.1",
"codedent": "^0.1.2",
"codemirror": "^6.0.1",
"eslint": "^9.13.0",
"flatted": "^3.3.1",
"rollup": "^4.24.2",
"eslint": "^9.15.0",
"flatted": "^3.3.2",
"rollup": "^4.27.4",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0",
"static-handler": "^0.5.3",
"string-width": "^7.2.0",
"typescript": "^5.6.3",
"xterm": "^5.3.0",
"xterm-readline": "^1.1.1"
"typescript": "^5.7.2",
"xterm-readline": "^1.1.2"
},
"repository": {
"type": "git",

View File

@@ -46,7 +46,7 @@ const modules = {
"toml.js": join(node_modules, "@webreflection", "toml-j0.4", "toml.js"),
// xterm
"xterm.js": resolve("xterm"),
"xterm.js": resolve("@xterm/xterm"),
"xterm-readline.js": resolve("xterm-readline"),
"xterm_addon-fit.js": fetch(`${CDN}/@xterm/addon-fit/+esm`).then((b) =>
b.text(),
@@ -54,9 +54,9 @@ const modules = {
"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(),
),
"xterm.css": fetch(
`${CDN}/@xterm/xterm@${v("@xterm/xterm")}/css/xterm.min.css`,
).then((b) => b.text()),
// codemirror
"codemirror.js": reBundle("codemirror"),

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
/**
* Minified by jsDelivr using clean-css v5.3.2.
* Original file: /npm/xterm@5.3.0/css/xterm.css
* Original file: /npm/@xterm/xterm@5.5.0/css/xterm.css
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:transparent}.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}

File diff suppressed because one or more lines are too long

View File

@@ -73,3 +73,8 @@ mpy-config {
background-color: #fff;
animation: spinner 0.6s linear infinite;
}
py-terminal span,
mpy-terminal span {
letter-spacing: 0 !important;
}

View File

@@ -34,6 +34,9 @@ import {
inputFailure,
} from "./hooks.js";
import codemirror from "./plugins/codemirror.js";
export { codemirror };
import { stdlib, optional } from "./stdlib.js";
export { stdlib, optional, inputFailure };

View File

@@ -1,5 +1,10 @@
// ⚠️ This file is an artifact: DO NOT MODIFY
export default {
codemirror: () =>
import(
/* webpackIgnore: true */
"./plugins/codemirror.js"
),
["deprecations-manager"]: () =>
import(
/* webpackIgnore: true */

View File

@@ -0,0 +1,31 @@
// lazy loaded on-demand codemirror related files
export default {
get core() {
return import(/* webpackIgnore: true */ "../3rd-party/codemirror.js");
},
get state() {
return import(
/* webpackIgnore: true */ "../3rd-party/codemirror_state.js"
);
},
get python() {
return import(
/* webpackIgnore: true */ "../3rd-party/codemirror_lang-python.js"
);
},
get language() {
return import(
/* webpackIgnore: true */ "../3rd-party/codemirror_language.js"
);
},
get view() {
return import(
/* webpackIgnore: true */ "../3rd-party/codemirror_view.js"
);
},
get commands() {
return import(
/* webpackIgnore: true */ "../3rd-party/codemirror_commands.js"
);
},
};

View File

@@ -2,6 +2,7 @@
import { Hook, XWorker, dedent, defineProperties } from "polyscript/exports";
import { TYPES, offline_interpreter, relative_url, stdlib } from "../core.js";
import { notify } from "./error.js";
import codemirror from "./codemirror.js";
const RUN_BUTTON = `<svg style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>`;
@@ -194,14 +195,12 @@ const init = async (script, type, interpreter) => {
{ keymap },
{ defaultKeymap, indentWithTab },
] = await Promise.all([
import(/* webpackIgnore: true */ "../3rd-party/codemirror.js"),
import(/* webpackIgnore: true */ "../3rd-party/codemirror_state.js"),
import(
/* webpackIgnore: true */ "../3rd-party/codemirror_lang-python.js"
),
import(/* webpackIgnore: true */ "../3rd-party/codemirror_language.js"),
import(/* webpackIgnore: true */ "../3rd-party/codemirror_view.js"),
import(/* webpackIgnore: true */ "../3rd-party/codemirror_commands.js"),
codemirror.core,
codemirror.state,
codemirror.python,
codemirror.language,
codemirror.view,
codemirror.commands,
]);
let isSetup = script.hasAttribute("setup");

View File

@@ -34,6 +34,8 @@ const workerReady = ({ interpreter, io, run, type }, { sync }) => {
pyterminal_write(String(error.message || error));
};
sync.pyterminal_stream_write = () => {};
// tiny shim of the code module with only interact
// to bootstrap a REPL like environment
interpreter.registerJsModule("code", {
@@ -71,6 +73,7 @@ export default async (element) => {
disableStdin: false,
cursorBlink: true,
cursorStyle: "block",
lineHeight: 1.2,
};
let stream;

View File

@@ -126,6 +126,7 @@ export default async (element) => {
disableStdin: false,
cursorBlink: true,
cursorStyle: "block",
lineHeight: 1.2,
});
xworker.sync.is_pyterminal = () => true;
@@ -136,6 +137,18 @@ export default async (element) => {
// setup remote thread JS/Python code for whenever the
// worker is ready to become a terminal
hooks.worker.onReady.add(workerReady);
// @see https://github.com/pyscript/pyscript/issues/2246
const patchInput = [
"import builtins as _b",
"from pyscript import sync as _s",
"_b.input = _s.pyterminal_read",
"del _b",
"del _s",
].join("\n");
hooks.worker.codeBeforeRun.add(patchInput);
hooks.worker.codeBeforeRunAsync.add(patchInput);
} else {
// in the main case, just bootstrap XTerm without
// allowing any input as that's not possible / awkward

File diff suppressed because one or more lines are too long

View File

@@ -30,8 +30,6 @@
# as it works transparently in both the main thread and worker cases.
from polyscript import lazy_py_modules as py_import
from pyscript.display import HTML, display
from pyscript.fetch import fetch
from pyscript.magic_js import (
RUNNING_IN_WORKER,
PyWorker,
@@ -43,19 +41,11 @@ from pyscript.magic_js import (
sync,
window,
)
from pyscript.display import HTML, display
from pyscript.fetch import fetch
from pyscript.storage import Storage, storage
from pyscript.websocket import WebSocket
from pyscript.events import when, Event
if not RUNNING_IN_WORKER:
from pyscript.workers import create_named_worker, workers
try:
from pyscript.event_handling import when
except:
# TODO: should we remove this? Or at the very least, we should capture
# the traceback otherwise it's very hard to debug
from pyscript.util import NotSupported
when = NotSupported(
"pyscript.when", "pyscript.when currently not available with this interpreter"
)

View File

@@ -1,76 +0,0 @@
import inspect
try:
from pyodide.ffi.wrappers import add_event_listener
except ImportError:
def add_event_listener(el, event_type, func):
el.addEventListener(event_type, func)
from pyscript.magic_js import document
def when(event_type=None, selector=None):
"""
Decorates a function and passes py-* events to the decorated function
The events might or not be an argument of the decorated function
"""
def decorator(func):
from pyscript.web import Element, ElementCollection
if isinstance(selector, str):
elements = document.querySelectorAll(selector)
# TODO: This is a hack that will be removed when pyscript becomes a package
# and we can better manage the imports without circular dependencies
elif isinstance(selector, Element):
elements = [selector._dom_element]
elif isinstance(selector, ElementCollection):
elements = [el._dom_element for el in selector]
else:
if isinstance(selector, list):
elements = selector
else:
elements = [selector]
try:
sig = inspect.signature(func)
# Function doesn't receive events
if not sig.parameters:
# Function is async: must be awaited
if inspect.iscoroutinefunction(func):
async def wrapper(*args, **kwargs):
await func()
else:
def wrapper(*args, **kwargs):
func()
else:
wrapper = func
except AttributeError:
# TODO: this is very ugly hack to get micropython working because inspect.signature
# doesn't exist, but we need to actually properly replace inspect.signature.
# It may be actually better to not try any magic for now and raise the error
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except TypeError as e:
if "takes" in str(e) and "positional arguments" in str(e):
return func()
raise
for el in elements:
add_event_listener(el, event_type, wrapper)
return func
return decorator

View File

@@ -0,0 +1,166 @@
import asyncio
import inspect
import sys
from functools import wraps
from pyscript.magic_js import document
from pyscript.ffi import create_proxy
from pyscript.util import is_awaitable
from pyscript import config
class Event:
"""
Represents something that may happen at some point in the future.
"""
def __init__(self):
self._listeners = []
def trigger(self, result):
"""
Trigger the event with a result to pass into the handlers.
"""
for listener in self._listeners:
if is_awaitable(listener):
# Use create task to avoid making this an async function.
asyncio.create_task(listener(result))
else:
listener(result)
def add_listener(self, listener):
"""
Add a callable/awaitable to listen to when this event is triggered.
"""
if is_awaitable(listener) or callable(listener):
if listener not in self._listeners:
self._listeners.append(listener)
else:
raise ValueError("Listener must be callable or awaitable.")
def remove_listener(self, *args):
"""
Clear the specified handler functions in *args. If no handlers
provided, clear all handlers.
"""
if args:
for listener in args:
self._listeners.remove(listener)
else:
self._listeners = []
def when(target, *args, **kwargs):
"""
Add an event listener to the target element(s) for the specified event type.
The target can be a string representing the event type, or an Event object.
If the target is an Event object, the event listener will be added to that
object. If the target is a string, the event listener will be added to the
element(s) that match the (second) selector argument.
If a (third) handler argument is provided, it will be called when the event
is triggered; thus allowing this to be used as both a function and a
decorator.
"""
# If "when" is called as a function, try to grab the handler from the
# arguments. If there's no handler, this must be a decorator based call.
handler = None
if args and (callable(args[0]) or is_awaitable(args[0])):
handler = args[0]
elif callable(kwargs.get("handler")) or is_awaitable(kwargs.get("handler")):
handler = kwargs.pop("handler")
# If the target is a string, it is the "older" use of `when` where it
# represents the name of a DOM event.
if isinstance(target, str):
# Extract the selector from the arguments or keyword arguments.
selector = args[0] if args else kwargs.pop("selector")
if not selector:
raise ValueError("No selector provided.")
# Grab the DOM elements to which the target event will be attached.
from pyscript.web import Element, ElementCollection
if isinstance(selector, str):
elements = document.querySelectorAll(selector)
elif isinstance(selector, Element):
elements = [selector._dom_element]
elif isinstance(selector, ElementCollection):
elements = [el._dom_element for el in selector]
else:
elements = selector if isinstance(selector, list) else [selector]
def decorator(func):
if config["type"] == "mpy": # Is MicroPython?
if is_awaitable(func):
async def wrapper(*args, **kwargs):
"""
This is a very ugly hack to get micropython working because
`inspect.signature` doesn't exist. It may be actually better
to not try any magic for now and raise the error.
"""
try:
return await func(*args, **kwargs)
except TypeError as e:
if "takes" in str(e) and "positional arguments" in str(e):
return await func()
raise
else:
def wrapper(*args, **kwargs):
"""
This is a very ugly hack to get micropython working because
`inspect.signature` doesn't exist. It may be actually better
to not try any magic for now and raise the error.
"""
try:
return func(*args, **kwargs)
except TypeError as e:
if "takes" in str(e) and "positional arguments" in str(e):
return func()
raise
else:
sig = inspect.signature(func)
if sig.parameters:
if is_awaitable(func):
async def wrapper(event):
return await func(event)
else:
wrapper = func
else:
# Function doesn't receive events.
if is_awaitable(func):
async def wrapper(*args, **kwargs):
return await func()
else:
def wrapper(*args, **kwargs):
return func()
wrapper = wraps(func)(wrapper)
if isinstance(target, Event):
# The target is a single Event object.
target.add_listener(wrapper)
elif isinstance(target, list) and all(isinstance(t, Event) for t in target):
# The target is a list of Event objects.
for evt in target:
evt.add_listener(wrapper)
else:
# The target is a string representing an event type, and so a
# DOM element or collection of elements is found in "elements".
for el in elements:
el.addEventListener(target, create_proxy(wrapper))
return wrapper
# If "when" was called as a decorator, return the decorator function,
# otherwise just call the internal decorator function with the supplied
# handler.
return decorator(handler) if handler else decorator

View File

@@ -1,7 +1,13 @@
import js
import sys
import inspect
def as_bytearray(buffer):
"""
Given a JavaScript ArrayBuffer, convert it to a Python bytearray in a
MicroPython friendly manner.
"""
ui8a = js.Uint8Array.new(buffer)
size = ui8a.length
ba = bytearray(size)
@@ -31,3 +37,22 @@ class NotSupported:
def __call__(self, *args):
raise TypeError(self.error)
def is_awaitable(obj):
"""
Returns a boolean indication if the passed in obj is an awaitable
function. (MicroPython treats awaitables as generator functions, and if
the object is a closure containing an async function we need to work
carefully.)
"""
from pyscript import config
if config["type"] == "mpy": # Is MicroPython?
# MicroPython doesn't appear to have a way to determine if a closure is
# an async function except via the repr. This is a bit hacky.
if "<closure <generator>" in repr(obj):
return True
return inspect.isgeneratorfunction(obj)
return inspect.iscoroutinefunction(obj)

View File

@@ -2,7 +2,8 @@
# `when` is not used in this module. It is imported here save the user an additional
# import (i.e. they can get what they need from `pyscript.web`).
from pyscript import document, when # NOQA
from pyscript import document, when, Event # NOQA
from pyscript.ffi import create_proxy
def wrap_dom_element(dom_element):
@@ -68,6 +69,18 @@ class Element:
type(self).get_tag_name()
)
# HTML on_events attached to the element become pyscript.Event instances.
self._on_events = {}
# Handle kwargs for handling named events with a default handler function.
properties = {}
for name, handler in kwargs.items():
if name.startswith("on_"):
ev = self.get_event(name) # Create the default Event instance.
ev.add_listener(handler)
else:
properties[name] = handler
# A set-like interface to the element's `classList`.
self._classes = Classes(self)
@@ -75,7 +88,7 @@ class Element:
self._style = Style(self)
# Set any specified classes, styles, and DOM properties.
self.update(classes=classes, style=style, **kwargs)
self.update(classes=classes, style=style, **properties)
def __eq__(self, obj):
"""Check for equality by comparing the underlying DOM element."""
@@ -93,13 +106,21 @@ class Element:
return self.find(key)
def __getattr__(self, name):
"""
Get an attribute from the element.
If the attribute is an event (e.g. "on_click"), we wrap it in an `Event`
instance and return that. Otherwise, we return the attribute from the
underlying DOM element.
"""
if name.startswith("on_"):
return self.get_event(name)
# This allows us to get attributes on the underlying DOM element that clash
# with Python keywords or built-ins (e.g. the output element has an
# attribute `for` which is a Python keyword, so you can access it on the
# Element instance via `for_`).
if name.endswith("_"):
name = name[:-1]
return getattr(self._dom_element, name)
def __setattr__(self, name, value):
@@ -119,8 +140,33 @@ class Element:
if name.endswith("_"):
name = name[:-1]
if name.startswith("on_"):
# Ensure on-events are cached in the _on_events dict if the
# user is setting them directly.
self._on_events[name] = value
setattr(self._dom_element, name, value)
def get_event(self, name):
"""
Get an `Event` instance for the specified event name.
"""
if not name.startswith("on_"):
raise ValueError("Event names must start with 'on_'.")
event_name = name[3:] # Remove the "on_" prefix.
if not hasattr(self._dom_element, event_name):
raise ValueError(f"Element has no '{event_name}' event.")
if name in self._on_events:
return self._on_events[name]
# Such an on-event exists in the DOM element, but we haven't yet
# wrapped it in an Event instance. Let's do that now. When the
# underlying DOM element's event is triggered, the Event instance
# will be triggered too.
ev = Event()
self._on_events[name] = ev
self._dom_element.addEventListener(event_name, create_proxy(ev.trigger))
return ev
@property
def children(self):
"""Return the element's children as an `ElementCollection`."""

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../../../dist/core.css">
<script type="module" src="../../../dist/core.js"></script>
</head>
<body>
<script type="py" config='{"packages":["jsonpointer==3.0.0"]}'>
import jsonpointer
from pyscript import document
document.documentElement.classList.add("done")
document.body.append("OK")
</script>
</body>
</html>

View File

@@ -139,6 +139,25 @@ test('Pyodide lockFileURL vs CDN', async ({ page }) => {
await expect(body).toBe('OK');
});
test('Pyodide pinned lockFileURL', async ({ page }) => {
const logs = [];
page.on('console', msg => {
const text = msg.text();
if (!text.startsWith('['))
logs.push(text);
});
await page.goto('http://localhost:8080/tests/javascript/pyodide-lockfile/');
await page.waitForSelector('html.done');
let body = await page.evaluate(() => document.body.lastChild.textContent);
await expect(body).toBe('OK');
await expect(!!logs.splice(0).length).toBe(true);
await page.reload();
await page.waitForSelector('html.done');
body = await page.evaluate(() => document.body.lastChild.textContent);
await expect(body).toBe('OK');
await expect(logs.splice(0).length).toBe(0);
});
test('MicroPython buffered error', async ({ page }) => {
await page.goto('http://localhost:8080/tests/javascript/mpy-error.html');
await page.waitForSelector('html.ok');

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy" src="emoji.py" terminal worker></script>
<script type="py" src="emoji.py" terminal worker></script>
</body>
</html>

View File

@@ -0,0 +1,17 @@
import sys
print(sys.version)
RED = chr(0x1F534) # LARGE RED CIRCLE
GREEN = chr(0x1F7E2) # LARGE GREEN CIRCLE
MOUSE = chr(0x1F42D) # MOUSE FACE
EARTH = chr(0x1F30E) # EARTH GLOBE AMERICAS
FACE = chr(0x1F610) # NEUTRAL FACE
BASMALA = chr(0xFDFD) # ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM
print("[", RED, "]")
print("[", MOUSE, "]")
print("[", EARTH, "]")
print("[", FACE, "]")
print("[", FACE * 3, "]")
print("[", BASMALA, "]")
print("[", BASMALA + GREEN, "]")

View File

@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script type="module" src="../../../dist/core.js"></script>
</head>
<body>
<script type="py" src="./main.py" terminal worker></script>
</body>
</html>

View File

@@ -0,0 +1 @@
print(input("What food would you like me to get from the shop? "))

View File

@@ -62,6 +62,7 @@
<button id="a-test-button">I'm a button to be clicked</button>
<button>I'm another button you can click</button>
<button id="a-third-button">2 is better than 3 :)</button>
<button id="another-test-button">I'm another button to be clicked</button>
<div id="element-append-tests"></div>
<p class="collection"></p>

View File

@@ -1,6 +1,6 @@
{
"files": {
"https://raw.githubusercontent.com/ntoll/upytest/1.0.8/upytest.py": "",
"https://raw.githubusercontent.com/ntoll/upytest/1.0.9/upytest.py": "",
"./tests/test_config.py": "tests/test_config.py",
"./tests/test_current_target.py": "tests/test_current_target.py",
"./tests/test_display.py": "tests/test_display.py",
@@ -12,7 +12,7 @@
"./tests/test_running_in_worker.py": "tests/test_running_in_worker.py",
"./tests/test_web.py": "tests/test_web.py",
"./tests/test_websocket.py": "tests/test_websocket.py",
"./tests/test_when.py": "tests/test_when.py",
"./tests/test_events.py": "tests/test_events.py",
"./tests/test_window.py": "tests/test_window.py"
},
"js_modules": {

View File

@@ -1,6 +1,6 @@
{
"files": {
"https://raw.githubusercontent.com/ntoll/upytest/1.0.8/upytest.py": "",
"https://raw.githubusercontent.com/ntoll/upytest/1.0.9/upytest.py": "",
"./tests/test_config.py": "tests/test_config.py",
"./tests/test_current_target.py": "tests/test_current_target.py",
"./tests/test_display.py": "tests/test_display.py",
@@ -12,7 +12,7 @@
"./tests/test_running_in_worker.py": "tests/test_running_in_worker.py",
"./tests/test_web.py": "tests/test_web.py",
"./tests/test_websocket.py": "tests/test_websocket.py",
"./tests/test_when.py": "tests/test_when.py",
"./tests/test_events.py": "tests/test_events.py",
"./tests/test_window.py": "tests/test_window.py"
},
"js_modules": {

View File

@@ -0,0 +1,360 @@
"""
Tests for the when function and Event class.
"""
import asyncio
import upytest
from pyscript import RUNNING_IN_WORKER, web, Event, when
def get_container():
return web.page.find("#test-element-container")[0]
def setup():
container = get_container()
container.innerHTML = ""
def teardown():
container = get_container()
container.innerHTML = ""
def test_event_add_listener():
"""
Adding a listener to an event should add it to the list of listeners. It
should only be added once.
"""
event = Event()
listener = lambda x: x
event.add_listener(listener)
event.add_listener(listener)
assert len(event._listeners) == 1 # Only one item added.
assert listener in event._listeners # The item is the expected listener.
def test_event_remove_listener():
"""
Removing a listener from an event should remove it from the list of
listeners.
"""
event = Event()
listener1 = lambda x: x
listener2 = lambda x: x
event.add_listener(listener1)
event.add_listener(listener2)
assert len(event._listeners) == 2 # Two listeners added.
assert listener1 in event._listeners # The first listener is in the list.
assert listener2 in event._listeners # The second listener is in the list.
event.remove_listener(listener1)
assert len(event._listeners) == 1 # Only one item remains.
assert listener2 in event._listeners # The second listener is in the list.
def test_event_remove_all_listeners():
"""
Removing all listeners from an event should clear the list of listeners.
"""
event = Event()
listener1 = lambda x: x
listener2 = lambda x: x
event.add_listener(listener1)
event.add_listener(listener2)
assert len(event._listeners) == 2 # Two listeners added.
event.remove_listener()
assert len(event._listeners) == 0 # No listeners remain.
def test_event_trigger():
"""
Triggering an event should call all of the listeners with the provided
arguments.
"""
event = Event()
counter = 0
def listener(x):
nonlocal counter
counter += 1
assert x == "ok"
event.add_listener(listener)
assert counter == 0 # The listener has not been triggered yet.
event.trigger("ok")
assert counter == 1 # The listener has been triggered with the expected result.
async def test_event_trigger_with_awaitable():
"""
Triggering an event with an awaitable listener should call the listener
with the provided arguments.
"""
call_flag = asyncio.Event()
event = Event()
counter = 0
async def listener(x):
nonlocal counter
counter += 1
assert x == "ok"
call_flag.set()
event.add_listener(listener)
assert counter == 0 # The listener has not been triggered yet.
event.trigger("ok")
await call_flag.wait()
assert counter == 1 # The listener has been triggered with the expected result.
async def test_when_decorator_with_event():
"""
When the decorated function takes a single parameter,
it should be passed the event object.
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
called = False
call_flag = asyncio.Event()
@when("click", selector="#foo_id")
def foo(evt):
nonlocal called
called = evt
call_flag.set()
btn.click()
await call_flag.wait()
assert called.target.id == "foo_id"
async def test_when_decorator_without_event():
"""
When the decorated function takes no parameters (not including 'self'),
it should be called without the event object.
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
called = False
call_flag = asyncio.Event()
@web.when("click", selector="#foo_id")
def foo():
nonlocal called
called = True
call_flag.set()
btn.click()
await call_flag.wait()
assert called is True
async def test_when_decorator_with_event_as_async_handler():
"""
When the decorated function takes a single parameter,
it should be passed the event object. Async version.
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
called = False
call_flag = asyncio.Event()
@when("click", selector="#foo_id")
async def foo(evt):
nonlocal called
called = evt
call_flag.set()
btn.click()
await call_flag.wait()
assert called.target.id == "foo_id"
async def test_when_decorator_without_event_as_async_handler():
"""
When the decorated function takes no parameters (not including 'self'),
it should be called without the event object. Async version.
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
called = False
call_flag = asyncio.Event()
@web.when("click", selector="#foo_id")
async def foo():
nonlocal called
called = True
call_flag.set()
btn.click()
await call_flag.wait()
assert called is True
async def test_two_when_decorators():
"""
When decorating a function twice, both should function
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
called1 = False
called2 = False
call_flag1 = asyncio.Event()
call_flag2 = asyncio.Event()
@when("click", selector="#foo_id")
def foo1(evt):
nonlocal called1
called1 = True
call_flag1.set()
@when("click", selector="#foo_id")
def foo2(evt):
nonlocal called2
called2 = True
call_flag2.set()
btn.click()
await call_flag1.wait()
await call_flag2.wait()
assert called1
assert called2
async def test_when_decorator_multiple_elements():
"""
The @when decorator's selector should successfully select multiple
DOM elements
"""
btn1 = web.button(
"foo_button1",
id="foo_id1",
classes=[
"foo_class",
],
)
btn2 = web.button(
"foo_button2",
id="foo_id2",
classes=[
"foo_class",
],
)
container = get_container()
container.append(btn1)
container.append(btn2)
counter = 0
call_flag1 = asyncio.Event()
call_flag2 = asyncio.Event()
@when("click", selector=".foo_class")
def foo(evt):
nonlocal counter
counter += 1
if evt.target.id == "foo_id1":
call_flag1.set()
else:
call_flag2.set()
assert counter == 0, counter
btn1.click()
await call_flag1.wait()
assert counter == 1, counter
btn2.click()
await call_flag2.wait()
assert counter == 2, counter
@upytest.skip(
"Only works in Pyodide on main thread",
skip_when=upytest.is_micropython or RUNNING_IN_WORKER,
)
def test_when_decorator_invalid_selector():
"""
When the selector parameter of @when is invalid, it should raise an error.
"""
if upytest.is_micropython:
from jsffi import JsException
else:
from pyodide.ffi import JsException
with upytest.raises(JsException) as e:
@when("click", selector="#.bad")
def foo(evt): ...
assert "'#.bad' is not a valid selector" in str(e.exception), str(e.exception)
def test_when_decorates_an_event():
"""
When the @when decorator is used on a function to handle an Event instance,
the function should be called when the Event object is triggered.
"""
whenable = Event()
counter = 0
# When as a decorator.
@when(whenable)
def handler(result):
"""
A function that should be called when the whenable object is triggered.
The result generated by the whenable object should be passed to the
function.
"""
nonlocal counter
counter += 1
assert result == "ok"
# The function should not be called until the whenable object is triggered.
assert counter == 0
# Trigger the whenable object.
whenable.trigger("ok")
# The function should have been called when the whenable object was
# triggered.
assert counter == 1
def test_when_called_with_an_event_and_handler():
"""
The when function should be able to be called with an Event object,
and a handler function.
"""
whenable = Event()
counter = 0
def handler(result):
"""
A function that should be called when the whenable object is triggered.
The result generated by the whenable object should be passed to the
function.
"""
nonlocal counter
counter += 1
assert result == "ok"
# When as a function.
when(whenable, handler)
# The function should not be called until the whenable object is triggered.
assert counter == 0
# Trigger the whenable object.
whenable.trigger("ok")
# The function should have been called when the whenable object was
# triggered.
assert counter == 1

View File

@@ -0,0 +1,48 @@
import upytest
import js
from pyscript import util
def test_as_bytearray():
"""
Test the as_bytearray function correctly converts a JavaScript ArrayBuffer
to a Python bytearray.
"""
msg = b"Hello, world!"
buffer = js.ArrayBuffer.new(len(msg))
ui8a = js.Uint8Array.new(buffer)
for b in msg:
ui8a[i] = b
ba = util.as_bytearray(buffer)
assert isinstance(ba, bytearray)
assert ba == msg
def test_not_supported():
"""
Test the NotSupported class raises an exception when trying to access
attributes or call the object.
"""
ns = util.NotSupported("test", "This is not supported.")
with upytest.raises(AttributeError) as e:
ns.test
assert str(e.exception) == "This is not supported.", str(e.exception)
with upytest.raises(AttributeError) as e:
ns.test = 1
assert str(e.exception) == "This is not supported.", str(e.exception)
with upytest.raises(TypeError) as e:
ns()
assert str(e.exception) == "This is not supported.", str(e.exception)
def test_is_awaitable():
"""
Test the is_awaitable function correctly identifies an asynchronous
function.
"""
async def async_func():
yield
assert util.is_awaitable(async_func)
assert not util.is_awaitable(lambda: None)

View File

@@ -164,6 +164,57 @@ class TestElement:
await call_flag.wait()
assert called
async def test_when_decorator_on_event(self):
called = False
another_button = web.page.find("#another-test-button")[0]
call_flag = asyncio.Event()
assert another_button.on_click is not None
assert isinstance(another_button.on_click, web.Event)
@when(another_button.on_click)
def on_click(event):
nonlocal called
called = True
call_flag.set()
# Now let's simulate a click on the button (using the low level JS API)
# so we don't risk dom getting in the way
assert not called
another_button._dom_element.click()
await call_flag.wait()
assert called
async def test_on_event_with_default_handler(self):
called = False
call_flag = asyncio.Event()
def handler(event):
nonlocal called
called = True
call_flag.set()
b = web.button("Click me", on_click=handler)
# Now let's simulate a click on the button (using the low level JS API)
# so we don't risk dom getting in the way
assert not called
b._dom_element.click()
await call_flag.wait()
assert called
def test_on_event_must_be_actual_event(self):
"""
Any on_FOO event must relate to an actual FOO event on the element.
"""
b = web.button("Click me")
# Non-existent event causes a ValueError
with upytest.raises(ValueError):
b.on_chicken
# Buttons have an underlying "click" event so this will work.
assert b.on_click
def test_inner_html_attribute(self):
# GIVEN an existing element on the page with a known empty text content
div = web.page.find("#element_attribute_tests")[0]
@@ -227,11 +278,15 @@ class TestCollection:
assert el.style["background-color"] != "red"
assert elements[i].style["background-color"] != "red"
@upytest.skip(
"Flakey in Pyodide on Worker",
skip_when=RUNNING_IN_WORKER and not upytest.is_micropython,
)
async def test_when_decorator(self):
called = False
call_flag = asyncio.Event()
buttons_collection = web.page.find("button")
buttons_collection = web.page["button"]
@when("click", buttons_collection)
def on_click(event):
@@ -249,6 +304,28 @@ class TestCollection:
called = False
call_flag.clear()
async def test_when_decorator_on_event(self):
call_counter = 0
call_flag = asyncio.Event()
buttons_collection = web.page.find("button")
number_of_clicks = len(buttons_collection)
@when(buttons_collection.on_click)
def on_click(event):
nonlocal call_counter
call_counter += 1
if call_counter == number_of_clicks:
call_flag.set()
# Now let's simulate a click on the button (using the low level JS API)
# so we don't risk dom getting in the way
assert call_counter == 0
for button in buttons_collection:
button._dom_element.click()
await call_flag.wait()
assert call_counter == number_of_clicks
class TestCreation:
@@ -759,14 +836,13 @@ class TestElements:
self._create_el_and_basic_asserts("iframe", properties=properties)
@upytest.skip(
"Flakey on Pyodide in worker.",
skip_when=RUNNING_IN_WORKER and not upytest.is_micropython,
"Flakey in worker.",
skip_when=RUNNING_IN_WORKER,
)
async def test_img(self):
"""
This test contains a bespoke version of the _create_el_and_basic_asserts
function so we can await asyncio.sleep if in a worker, so the DOM state
is in sync with the worker before property based asserts can happen.
This test, thanks to downloading an image from the internet, is flakey
when run in a worker. It's skipped when running in a worker.
"""
properties = {
"src": "https://picsum.photos/600/400",
@@ -774,39 +850,7 @@ class TestElements:
"width": 250,
"height": 200,
}
def parse_value(v):
if isinstance(v, bool):
return str(v)
return f"{v}"
args = []
kwargs = {}
if properties:
kwargs = {k: parse_value(v) for k, v in properties.items()}
# Let's make sure the target div to contain the element is empty.
container = web.page["#test-element-container"][0]
container.innerHTML = ""
assert container.innerHTML == "", container.innerHTML
# Let's create the element
try:
klass = getattr(web, "img")
el = klass(*args, **kwargs)
container.append(el)
except Exception as e:
assert False, f"Failed to create element img: {e}"
if RUNNING_IN_WORKER:
# Needed to sync the DOM with the worker.
await asyncio.sleep(0.5)
# Check the img element was created correctly and all its properties
# were set correctly.
for k, v in properties.items():
assert v == getattr(el, k), f"{k} should be {v} but is {getattr(el, k)}"
self._create_el_and_basic_asserts("img", properties=properties)
def test_input(self):
# TODO: we need multiple input tests

View File

@@ -1,216 +0,0 @@
"""
Tests for the pyscript.when decorator.
"""
import asyncio
import upytest
from pyscript import RUNNING_IN_WORKER, web
def get_container():
return web.page.find("#test-element-container")[0]
def setup():
container = get_container()
container.innerHTML = ""
def teardown():
container = get_container()
container.innerHTML = ""
async def test_when_decorator_with_event():
"""
When the decorated function takes a single parameter,
it should be passed the event object
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
called = False
call_flag = asyncio.Event()
@web.when("click", selector="#foo_id")
def foo(evt):
nonlocal called
called = evt
call_flag.set()
btn.click()
await call_flag.wait()
assert called.target.id == "foo_id"
async def test_when_decorator_without_event():
"""
When the decorated function takes no parameters (not including 'self'),
it should be called without the event object
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
called = False
call_flag = asyncio.Event()
@web.when("click", selector="#foo_id")
def foo():
nonlocal called
called = True
call_flag.set()
btn.click()
await call_flag.wait()
assert called
async def test_two_when_decorators():
"""
When decorating a function twice, both should function
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
called1 = False
called2 = False
call_flag1 = asyncio.Event()
call_flag2 = asyncio.Event()
@web.when("click", selector="#foo_id")
def foo1(evt):
nonlocal called1
called1 = True
call_flag1.set()
@web.when("click", selector="#foo_id")
def foo2(evt):
nonlocal called2
called2 = True
call_flag2.set()
btn.click()
await call_flag1.wait()
await call_flag2.wait()
assert called1
assert called2
async def test_two_when_decorators_same_element():
"""
When decorating a function twice *on the same DOM element*, both should
function
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
counter = 0
call_flag = asyncio.Event()
@web.when("click", selector="#foo_id")
@web.when("click", selector="#foo_id")
def foo(evt):
nonlocal counter
counter += 1
call_flag.set()
assert counter == 0, counter
btn.click()
await call_flag.wait()
assert counter == 2, counter
async def test_when_decorator_multiple_elements():
"""
The @when decorator's selector should successfully select multiple
DOM elements
"""
btn1 = web.button(
"foo_button1",
id="foo_id1",
classes=[
"foo_class",
],
)
btn2 = web.button(
"foo_button2",
id="foo_id2",
classes=[
"foo_class",
],
)
container = get_container()
container.append(btn1)
container.append(btn2)
counter = 0
call_flag1 = asyncio.Event()
call_flag2 = asyncio.Event()
@web.when("click", selector=".foo_class")
def foo(evt):
nonlocal counter
counter += 1
if evt.target.id == "foo_id1":
call_flag1.set()
else:
call_flag2.set()
assert counter == 0, counter
btn1.click()
await call_flag1.wait()
assert counter == 1, counter
btn2.click()
await call_flag2.wait()
assert counter == 2, counter
async def test_when_decorator_duplicate_selectors():
"""
When is not idempotent, so it should be possible to add multiple
@when decorators with the same selector.
"""
btn = web.button("foo_button", id="foo_id")
container = get_container()
container.append(btn)
counter = 0
call_flag = asyncio.Event()
@web.when("click", selector="#foo_id")
@web.when("click", selector="#foo_id") # duplicate
def foo1(evt):
nonlocal counter
counter += 1
call_flag.set()
assert counter == 0, counter
btn.click()
await call_flag.wait()
assert counter == 2, counter
@upytest.skip(
"Only works in Pyodide on main thread",
skip_when=upytest.is_micropython or RUNNING_IN_WORKER,
)
def test_when_decorator_invalid_selector():
"""
When the selector parameter of @when is invalid, it should raise an error.
"""
if upytest.is_micropython:
from jsffi import JsException
else:
from pyodide.ffi import JsException
with upytest.raises(JsException) as e:
@web.when("click", selector="#.bad")
def foo(evt): ...
assert "'#.bad' is not a valid selector" in str(e.exception), str(e.exception)

View File

@@ -1,6 +1,6 @@
declare var b: any;
declare var I: boolean;
declare namespace r {
declare var v: any;
declare var k: boolean;
declare namespace i {
export let __esModule: boolean;
export { Readline };
}
@@ -57,7 +57,7 @@ declare class Readline {
highlighter: any;
history: any;
promptSize: any;
layout: p;
layout: c;
buffer(): string;
shouldHighlight(): boolean;
clearScreen(): void;
@@ -124,15 +124,15 @@ declare class Readline {
readPaste(t: any): void;
readKey(t: any): void;
}
declare class p {
declare class c {
constructor(t: any);
promptSize: any;
cursor: c;
end: c;
cursor: u;
end: u;
}
declare class c {
declare class u {
constructor(t: any, e: any);
row: any;
col: any;
}
export { b as Readline, I as __esModule, r as default };
export { v as Readline, k as __esModule, i as default };

View File

@@ -1,4 +1,4 @@
declare var i: any;
declare var s: any;
declare var t: {};
export { i as Terminal, s as __esModule, t as default };
declare var D: any;
declare var R: any;
declare var L: {};
export { D as Terminal, R as __esModule, L as default };

View File

@@ -7,6 +7,7 @@ export function donkey(options: any): Promise<{
kill: () => void;
}>;
export function offline_interpreter(config: any): string;
import codemirror from "./plugins/codemirror.js";
import { stdlib } from "./stdlib.js";
import { optional } from "./stdlib.js";
import { inputFailure } from "./hooks.js";
@@ -63,4 +64,4 @@ declare const exportedHooks: {
};
declare const exportedConfig: {};
declare const exportedWhenDefined: any;
export { stdlib, optional, inputFailure, TYPES, relative_url, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
export { codemirror, stdlib, optional, inputFailure, TYPES, relative_url, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };

View File

@@ -1,4 +1,5 @@
declare const _default: {
codemirror: () => Promise<typeof import("./plugins/codemirror.js")>;
"deprecations-manager": () => Promise<typeof import("./plugins/deprecations-manager.js")>;
donkey: () => Promise<typeof import("./plugins/donkey.js")>;
error: () => Promise<typeof import("./plugins/error.js")>;

9
core/types/plugins/codemirror.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
declare namespace _default {
const core: Promise<typeof import("../3rd-party/codemirror.js")>;
const state: Promise<typeof import("../3rd-party/codemirror_state.js")>;
const python: Promise<typeof import("../3rd-party/codemirror_lang-python.js")>;
const language: Promise<typeof import("../3rd-party/codemirror_language.js")>;
const view: Promise<typeof import("../3rd-party/codemirror_view.js")>;
const commands: Promise<typeof import("../3rd-party/codemirror_commands.js")>;
}
export default _default;

View File

@@ -2,7 +2,7 @@ declare namespace _default {
let pyscript: {
"__init__.py": string;
"display.py": string;
"event_handling.py": string;
"events.py": string;
"fetch.py": string;
"ffi.py": string;
"flatted.py": string;