@pyscript/core 0.4.11 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/{codemirror-HQuHfCf6.js → codemirror-ZDIr9iJj.js} +2 -2
  2. package/dist/{codemirror-HQuHfCf6.js.map → codemirror-ZDIr9iJj.js.map} +1 -1
  3. package/dist/{codemirror_commands-Be_sxRoS.js → codemirror_commands-NWDrre9v.js} +2 -2
  4. package/dist/{codemirror_commands-Be_sxRoS.js.map → codemirror_commands-NWDrre9v.js.map} +1 -1
  5. package/dist/{codemirror_lang-python-6K8_k_-l.js → codemirror_lang-python-dHfsE0jo.js} +2 -2
  6. package/dist/{codemirror_lang-python-6K8_k_-l.js.map → codemirror_lang-python-dHfsE0jo.js.map} +1 -1
  7. package/dist/{codemirror_language-xLdmYCLC.js → codemirror_language-BPcKWOkU.js} +2 -2
  8. package/dist/{codemirror_language-xLdmYCLC.js.map → codemirror_language-BPcKWOkU.js.map} +1 -1
  9. package/dist/codemirror_view-DJFeHd78.js +2 -0
  10. package/dist/codemirror_view-DJFeHd78.js.map +1 -0
  11. package/dist/core-iVubX18f.js +3 -0
  12. package/dist/core-iVubX18f.js.map +1 -0
  13. package/dist/core.js +1 -1
  14. package/dist/{deprecations-manager-VwzMNbZW.js → deprecations-manager-DiHgfBfD.js} +2 -2
  15. package/dist/{deprecations-manager-VwzMNbZW.js.map → deprecations-manager-DiHgfBfD.js.map} +1 -1
  16. package/dist/{error-BLGRrvy5.js → error-N-NzGsyV.js} +2 -2
  17. package/dist/{error-BLGRrvy5.js.map → error-N-NzGsyV.js.map} +1 -1
  18. package/dist/{index-DV62RffY.js → index-Db1V4drY.js} +2 -2
  19. package/dist/{index-DV62RffY.js.map → index-Db1V4drY.js.map} +1 -1
  20. package/dist/{py-editor-DZFxQiMs.js → py-editor-CNYhKu6L.js} +2 -2
  21. package/dist/{py-editor-DZFxQiMs.js.map → py-editor-CNYhKu6L.js.map} +1 -1
  22. package/dist/py-terminal-1UPKQyLd.js +2 -0
  23. package/dist/py-terminal-1UPKQyLd.js.map +1 -0
  24. package/dist/zip-BVYJ4_a2.js +2 -0
  25. package/dist/zip-BVYJ4_a2.js.map +1 -0
  26. package/package.json +5 -5
  27. package/src/plugins/py-terminal.js +179 -152
  28. package/types/plugins/py-terminal.d.ts +1 -2
  29. package/dist/codemirror_view-DnYSj9rm.js +0 -2
  30. package/dist/codemirror_view-DnYSj9rm.js.map +0 -1
  31. package/dist/core-Csu5eIPV.js +0 -3
  32. package/dist/core-Csu5eIPV.js.map +0 -1
  33. package/dist/py-terminal-B4fxy7K7.js +0 -2
  34. package/dist/py-terminal-B4fxy7K7.js.map +0 -1
  35. package/dist/zip-CVv62MiA.js +0 -2
  36. package/dist/zip-CVv62MiA.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyscript/core",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "type": "module",
5
5
  "description": "PyScript",
6
6
  "module": "./index.js",
@@ -42,17 +42,17 @@
42
42
  "dependencies": {
43
43
  "@ungap/with-resolvers": "^0.1.0",
44
44
  "basic-devtools": "^0.1.6",
45
- "polyscript": "^0.11.3",
45
+ "polyscript": "^0.12.2",
46
46
  "sticky-module": "^0.1.1",
47
47
  "to-json-callback": "^0.1.1",
48
48
  "type-checked-collections": "^0.1.7"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@codemirror/commands": "^6.3.3",
52
- "@codemirror/lang-python": "^6.1.4",
52
+ "@codemirror/lang-python": "^6.1.5",
53
53
  "@codemirror/language": "^6.10.1",
54
54
  "@codemirror/state": "^6.4.1",
55
- "@codemirror/view": "^6.26.0",
55
+ "@codemirror/view": "^6.26.1",
56
56
  "@playwright/test": "^1.42.1",
57
57
  "@rollup/plugin-commonjs": "^25.0.7",
58
58
  "@rollup/plugin-node-resolve": "^15.2.3",
@@ -62,7 +62,7 @@
62
62
  "chokidar": "^3.6.0",
63
63
  "codemirror": "^6.0.1",
64
64
  "eslint": "^8.57.0",
65
- "rollup": "^4.13.1",
65
+ "rollup": "^4.14.0",
66
66
  "rollup-plugin-postcss": "^4.0.2",
67
67
  "rollup-plugin-string": "^3.0.0",
68
68
  "static-handler": "^0.4.3",
@@ -1,11 +1,10 @@
1
1
  // PyScript py-terminal plugin
2
2
  import { TYPES, hooks } from "../core.js";
3
3
  import { notify } from "./error.js";
4
- import { defineProperty } from "polyscript/exports";
4
+ import { customObserver, defineProperty } from "polyscript/exports";
5
5
 
6
- const SELECTOR = [...TYPES.keys()]
7
- .map((type) => `script[type="${type}"][terminal],${type}-script[terminal]`)
8
- .join(",");
6
+ // will contain all valid selectors
7
+ const SELECTORS = [];
9
8
 
10
9
  // show the error on main and
11
10
  // stops the module from keep executing
@@ -14,8 +13,6 @@ const notifyAndThrow = (message) => {
14
13
  throw new Error(message);
15
14
  };
16
15
 
17
- const notParsedYet = (script) => !bootstrapped.has(script);
18
-
19
16
  const onceOnMain = ({ attributes: { worker } }) => !worker;
20
17
 
21
18
  const bootstrapped = new WeakSet();
@@ -25,63 +22,76 @@ let addStyle = true;
25
22
  // this callback will be serialized as string and it never needs
26
23
  // to be invoked multiple times. Each xworker here is bootstrapped
27
24
  // only once thanks to the `sync.is_pyterminal()` check.
28
- const workerReady = ({ interpreter, io, run }, { sync }) => {
25
+ const workerReady = ({ interpreter, io, run, type }, { sync }) => {
29
26
  if (!sync.is_pyterminal()) return;
30
27
 
31
28
  // in workers it's always safe to grab the polyscript currentScript
32
- run("from polyscript.currentScript import terminal as __terminal__");
29
+ // the ugly `_` dance is due MicroPython not able to import via:
30
+ // `from polyscript.currentScript import terminal as __terminal__`
31
+ run(
32
+ "from polyscript import currentScript as _; __terminal__ = _.terminal; del _",
33
+ );
33
34
 
34
- // This part is inevitably duplicated as external scope
35
- // can't be reached by workers out of the box.
36
- // The detail is that here we use sync though, not readline.
37
- const decoder = new TextDecoder();
38
35
  let data = "";
36
+ const { pyterminal_read, pyterminal_write } = sync;
37
+ const decoder = new TextDecoder();
39
38
  const generic = {
40
39
  isatty: true,
41
40
  write(buffer) {
42
41
  data = decoder.decode(buffer);
43
- sync.pyterminal_write(data);
42
+ pyterminal_write(data);
44
43
  return buffer.length;
45
44
  },
46
45
  };
47
- interpreter.setStdout(generic);
48
- interpreter.setStderr(generic);
49
- interpreter.setStdin({
50
- isatty: true,
51
- stdin: () => sync.pyterminal_read(data),
52
- });
53
46
 
47
+ // This part works already in both Pyodide and MicroPython
54
48
  io.stderr = (error) => {
55
- sync.pyterminal_write(`${error.message || error}\n`);
49
+ pyterminal_write(String(error.message || error));
56
50
  };
57
- };
58
51
 
59
- const pyTerminal = async () => {
60
- const terminals = document.querySelectorAll(SELECTOR);
61
-
62
- const unknown = [].filter.call(terminals, notParsedYet);
63
-
64
- // no results will look further for runtime nodes
65
- if (!unknown.length) return;
66
- // early flag elements as known to avoid concurrent
67
- // MutationObserver invokes of this async handler
68
- else unknown.forEach(bootstrapped.add, bootstrapped);
69
-
70
- // we currently support only one terminal as in "classic"
71
- if ([].filter.call(terminals, onceOnMain).length > 1)
72
- notifyAndThrow("You can use at most 1 main terminal");
73
-
74
- // import styles lazily
75
- if (addStyle) {
76
- addStyle = false;
77
- document.head.append(
78
- Object.assign(document.createElement("link"), {
79
- rel: "stylesheet",
80
- href: new URL("./xterm.css", import.meta.url),
81
- }),
82
- );
52
+ // MicroPython has no code or code.interact()
53
+ // This part patches it in a way that simulates
54
+ // the code.interact() module in Pyodide.
55
+ if (type === "mpy") {
56
+ io.stdout = generic.write;
57
+ // tiny shim of the code module with only interact
58
+ // to bootstrap a REPL like environment
59
+ interpreter.registerJsModule("code", {
60
+ interact() {
61
+ const encoder = new TextEncoder();
62
+ const acc = [];
63
+ let input = "";
64
+ let length = 1;
65
+ io.stdout = ([c]) => {
66
+ // avoid duplicating the output produced by the input
67
+ if (length++ > input.length) acc.push(c);
68
+ };
69
+ interpreter.replInit();
70
+ (function repl() {
71
+ const out = decoder.decode(new Uint8Array(acc.splice(0)));
72
+ pyterminal_write(out);
73
+ // print in current line only the last line produced by the REPL
74
+ data = out.split("\n").at(-1);
75
+ input = encoder.encode(`${pyterminal_read(data)}\r`);
76
+ length = 0;
77
+ for (const c of input)
78
+ interpreter.replProcessChar(c);
79
+ repl();
80
+ }());
81
+ },
82
+ });
83
83
  }
84
+ else {
85
+ interpreter.setStdout(generic);
86
+ interpreter.setStderr(generic);
87
+ interpreter.setStdin({
88
+ isatty: true,
89
+ stdin: () => pyterminal_read(data),
90
+ });
91
+ }
92
+ };
84
93
 
94
+ const pyTerminal = async (element) => {
85
95
  // lazy load these only when a valid terminal is found
86
96
  const [{ Terminal }, { Readline }, { FitAddon }] = await Promise.all([
87
97
  import(/* webpackIgnore: true */ "../3rd-party/xterm.js"),
@@ -89,118 +99,135 @@ const pyTerminal = async () => {
89
99
  import(/* webpackIgnore: true */ "../3rd-party/xterm_addon-fit.js"),
90
100
  ]);
91
101
 
92
- for (const element of unknown) {
93
- // hopefully to be removed in the near future!
94
- if (element.matches('script[type="mpy"],mpy-script'))
95
- notifyAndThrow("Unsupported terminal.");
96
-
97
- const readline = new Readline();
98
-
99
- // common main thread initialization for both worker
100
- // or main case, bootstrapping the terminal on its target
101
- const init = (options) => {
102
- let target = element;
103
- const selector = element.getAttribute("target");
104
- if (selector) {
105
- target =
106
- document.getElementById(selector) ||
107
- document.querySelector(selector);
108
- if (!target) throw new Error(`Unknown target ${selector}`);
109
- } else {
110
- target = document.createElement("py-terminal");
111
- target.style.display = "block";
112
- element.after(target);
113
- }
114
- const terminal = new Terminal({
115
- theme: {
116
- background: "#191A19",
117
- foreground: "#F5F2E7",
118
- },
119
- ...options,
120
- });
121
- const fitAddon = new FitAddon();
122
- terminal.loadAddon(fitAddon);
123
- terminal.loadAddon(readline);
124
- terminal.open(target);
125
- fitAddon.fit();
126
- terminal.focus();
127
- defineProperty(element, "terminal", { value: terminal });
128
- return terminal;
129
- };
130
-
131
- // branch logic for the worker
132
- if (element.hasAttribute("worker")) {
133
- // add a hook on the main thread to setup all sync helpers
134
- // also bootstrapping the XTerm target on main *BUT* ...
135
- hooks.main.onWorker.add(function worker(_, xworker) {
136
- // ... as multiple workers will add multiple callbacks
137
- // be sure no xworker is ever initialized twice!
138
- if (bootstrapped.has(xworker)) return;
139
- bootstrapped.add(xworker);
140
-
141
- // still cleanup this callback for future scripts/workers
142
- hooks.main.onWorker.delete(worker);
143
-
144
- init({
145
- disableStdin: false,
146
- cursorBlink: true,
147
- cursorStyle: "block",
148
- });
149
-
150
- xworker.sync.is_pyterminal = () => true;
151
- xworker.sync.pyterminal_read = readline.read.bind(readline);
152
- xworker.sync.pyterminal_write = readline.write.bind(readline);
102
+ const readline = new Readline();
103
+
104
+ // common main thread initialization for both worker
105
+ // or main case, bootstrapping the terminal on its target
106
+ const init = (options) => {
107
+ let target = element;
108
+ const selector = element.getAttribute("target");
109
+ if (selector) {
110
+ target =
111
+ document.getElementById(selector) ||
112
+ document.querySelector(selector);
113
+ if (!target) throw new Error(`Unknown target ${selector}`);
114
+ } else {
115
+ target = document.createElement("py-terminal");
116
+ target.style.display = "block";
117
+ element.after(target);
118
+ }
119
+ const terminal = new Terminal({
120
+ theme: {
121
+ background: "#191A19",
122
+ foreground: "#F5F2E7",
123
+ },
124
+ ...options,
125
+ });
126
+ const fitAddon = new FitAddon();
127
+ terminal.loadAddon(fitAddon);
128
+ terminal.loadAddon(readline);
129
+ terminal.open(target);
130
+ fitAddon.fit();
131
+ terminal.focus();
132
+ defineProperty(element, "terminal", { value: terminal });
133
+ return terminal;
134
+ };
135
+
136
+ // branch logic for the worker
137
+ if (element.hasAttribute("worker")) {
138
+ // add a hook on the main thread to setup all sync helpers
139
+ // also bootstrapping the XTerm target on main *BUT* ...
140
+ hooks.main.onWorker.add(function worker(_, xworker) {
141
+ // ... as multiple workers will add multiple callbacks
142
+ // be sure no xworker is ever initialized twice!
143
+ if (bootstrapped.has(xworker)) return;
144
+ bootstrapped.add(xworker);
145
+
146
+ // still cleanup this callback for future scripts/workers
147
+ hooks.main.onWorker.delete(worker);
148
+
149
+ init({
150
+ disableStdin: false,
151
+ cursorBlink: true,
152
+ cursorStyle: "block",
153
153
  });
154
154
 
155
- // setup remote thread JS/Python code for whenever the
156
- // worker is ready to become a terminal
157
- hooks.worker.onReady.add(workerReady);
158
- } else {
159
- // in the main case, just bootstrap XTerm without
160
- // allowing any input as that's not possible / awkward
161
- hooks.main.onReady.add(function main({ interpreter, io, run }) {
162
- console.warn("py-terminal is read only on main thread");
163
- hooks.main.onReady.delete(main);
164
-
165
- // on main, it's easy to trash and clean the current terminal
166
- globalThis.__py_terminal__ = init({
167
- disableStdin: true,
168
- cursorBlink: false,
169
- cursorStyle: "underline",
170
- });
171
- run("from js import __py_terminal__ as __terminal__");
172
- delete globalThis.__py_terminal__;
173
-
174
- // This part is inevitably duplicated as external scope
175
- // can't be reached by workers out of the box.
176
- // The detail is that here we use readline here, not sync.
177
- const decoder = new TextDecoder();
178
- let data = "";
179
- const generic = {
180
- isatty: true,
181
- write(buffer) {
182
- data = decoder.decode(buffer);
183
- readline.write(data);
184
- return buffer.length;
185
- },
186
- };
187
- interpreter.setStdout(generic);
188
- interpreter.setStderr(generic);
189
- interpreter.setStdin({
190
- isatty: true,
191
- stdin: () => readline.read(data),
192
- });
193
-
194
- io.stderr = (error) => {
195
- readline.write(`${error.message || error}\n`);
155
+ xworker.sync.is_pyterminal = () => true;
156
+ xworker.sync.pyterminal_read = readline.read.bind(readline);
157
+ xworker.sync.pyterminal_write = readline.write.bind(readline);
158
+ });
159
+
160
+ // setup remote thread JS/Python code for whenever the
161
+ // worker is ready to become a terminal
162
+ hooks.worker.onReady.add(workerReady);
163
+ } else {
164
+ // in the main case, just bootstrap XTerm without
165
+ // allowing any input as that's not possible / awkward
166
+ hooks.main.onReady.add(function main({ interpreter, io, run, type }) {
167
+ console.warn("py-terminal is read only on main thread");
168
+ hooks.main.onReady.delete(main);
169
+
170
+ // on main, it's easy to trash and clean the current terminal
171
+ globalThis.__py_terminal__ = init({
172
+ disableStdin: true,
173
+ cursorBlink: false,
174
+ cursorStyle: "underline",
175
+ });
176
+ run("from js import __py_terminal__ as __terminal__");
177
+ delete globalThis.__py_terminal__;
178
+
179
+ io.stderr = (error) => {
180
+ readline.write(String(error.message || error));
181
+ };
182
+
183
+ if (type === "mpy") {
184
+ interpreter.setStdin = Object; // as no-op
185
+ interpreter.setStderr = Object; // as no-op
186
+ interpreter.setStdout = ({ write }) => {
187
+ io.stdout = write;
196
188
  };
189
+ }
190
+
191
+ let data = "";
192
+ const decoder = new TextDecoder();
193
+ const generic = {
194
+ isatty: true,
195
+ write(buffer) {
196
+ data = decoder.decode(buffer);
197
+ readline.write(data);
198
+ return buffer.length;
199
+ },
200
+ };
201
+ interpreter.setStdout(generic);
202
+ interpreter.setStderr(generic);
203
+ interpreter.setStdin({
204
+ isatty: true,
205
+ stdin: () => readline.read(data),
197
206
  });
198
- }
207
+ });
199
208
  }
200
209
  };
201
210
 
202
- const mo = new MutationObserver(pyTerminal);
203
- mo.observe(document, { childList: true, subtree: true });
211
+ for (const key of TYPES.keys()) {
212
+ const selector = `script[type="${key}"][terminal],${key}-script[terminal]`;
213
+ SELECTORS.push(selector);
214
+ customObserver.set(selector, async (element) => {
215
+ // we currently support only one terminal on main as in "classic"
216
+ const terminals = document.querySelectorAll(SELECTORS.join(","));
217
+ if ([].filter.call(terminals, onceOnMain).length > 1)
218
+ notifyAndThrow("You can use at most 1 main terminal");
219
+
220
+ // import styles lazily
221
+ if (addStyle) {
222
+ addStyle = false;
223
+ document.head.append(
224
+ Object.assign(document.createElement("link"), {
225
+ rel: "stylesheet",
226
+ href: new URL("./xterm.css", import.meta.url),
227
+ }),
228
+ );
229
+ }
204
230
 
205
- // try to check the current document ASAP
206
- export default pyTerminal();
231
+ await pyTerminal(element);
232
+ });
233
+ }
@@ -1,2 +1 @@
1
- declare const _default: Promise<void>;
2
- export default _default;
1
+ export {};