1
1
import { JQueryTerminal } from "./JQueryTerminal" ;
2
2
import React , { useEffect , useRef } from "react" ;
3
- import { usePyodide } from "../hooks/usePyodide" ;
4
-
5
- import { terminal } from "jquery" ;
6
- import { useEnvironmentSetup } from "../hooks/useEnvironmentSetup" ;
3
+ import { EnvironmentStatus , useEnvironmentSetup } from "../hooks/useEnvironmentSetup" ;
4
+ import { PyodideStatus } from "../hooks/usePyodide" ;
5
+ import { PyodideInterface } from "pyodide" ;
7
6
7
+ // Taken from https://terminal.jcubic.pl/examples.php
8
8
function progress ( percent , width ) {
9
9
var size = Math . round ( width * percent / 100 ) ;
10
10
var left = '' , taken = '' , i ;
@@ -20,31 +20,188 @@ function progress(percent, width) {
20
20
return '[' + taken + left + '] ' + percent + '%' ;
21
21
}
22
22
23
+ // Terminal code largely taken from https://github.com/pyodide/pyodide/blob/main/src/templates/console.html
24
+ function sleep ( s ) {
25
+ return new Promise ( ( resolve ) => setTimeout ( resolve , s ) ) ;
26
+ }
27
+
28
+ function create_interpreter ( pyodide : PyodideInterface , term : JQueryTerminal ) {
29
+ let { repr_shorten, PyodideConsole} = pyodide . pyimport ( "pyodide.console" ) ;
30
+ const pyconsole = PyodideConsole ( pyodide . globals ) ;
31
+
32
+ const namespace = pyodide . globals . get ( "dict" ) ( ) ;
33
+ const await_fut = pyodide . runPython (
34
+ `
35
+ import builtins
36
+ from pyodide.ffi import to_js
37
+
38
+ async def await_fut(fut):
39
+ res = await fut
40
+ if res is not None:
41
+ builtins._ = res
42
+ return to_js([res], depth=1)
43
+
44
+ await_fut
45
+ ` ,
46
+ { globals : namespace } ,
47
+ ) ;
48
+ namespace . destroy ( ) ;
49
+
50
+ const echo = ( msg , ...opts ) => {
51
+ return term . echo (
52
+ msg
53
+ . replaceAll ( "]]" , "]]" )
54
+ . replaceAll ( "[[" , "[[" ) ,
55
+ ...opts ,
56
+ ) ;
57
+ } ;
58
+
59
+ async function lock ( ) {
60
+ let resolve ;
61
+ const ready = term . ready ;
62
+ term . ready = new Promise ( ( res ) => ( resolve = res ) ) ;
63
+ await ready ;
64
+ return resolve ;
65
+ }
66
+
67
+ const ps1 = ">>> " ;
68
+ const ps2 = "... " ;
69
+
70
+ async function interpreter ( command , term : JQueryTerminal ) {
71
+ const unlock = await lock ( ) ;
72
+ term . pause ( ) ;
73
+ // multiline should be split (useful when pasting)
74
+ for ( const c of command . split ( "\n" ) ) {
75
+ const escaped = c . replaceAll ( / \u00a0 / g, " " ) ;
76
+ const fut = pyconsole . push ( escaped ) ;
77
+ term . set_prompt ( fut . syntax_check === "incomplete" ? ps2 : ps1 ) ;
78
+ switch ( fut . syntax_check ) {
79
+ case "syntax-error" :
80
+ term . error ( fut . formatted_error . trimEnd ( ) ) ;
81
+ continue ;
82
+ case "incomplete" :
83
+ continue ;
84
+ case "complete" :
85
+ break ;
86
+ default :
87
+ throw new Error ( `Unexpected type ${ ty } ` ) ;
88
+ }
89
+ // In JavaScript, await automatically also awaits any results of
90
+ // awaits, so if an async function returns a future, it will await
91
+ // the inner future too. This is not what we want so we
92
+ // temporarily put it into a list to protect it.
93
+ const wrapped = await_fut ( fut ) ;
94
+ // complete case, get result / error and print it.
95
+ try {
96
+ const [ value ] = await wrapped ;
97
+ if ( value !== undefined ) {
98
+ echo (
99
+ repr_shorten . callKwargs ( value , {
100
+ separator : "\n<long output truncated>\n" ,
101
+ } ) ,
102
+ ) ;
103
+ }
104
+ if ( value instanceof pyodide . ffi . PyProxy ) {
105
+ value . destroy ( ) ;
106
+ }
107
+ } catch ( e ) {
108
+ if ( e . constructor . name === "PythonError" ) {
109
+ const message = fut . formatted_error || e . message ;
110
+ term . error ( message . trimEnd ( ) ) ;
111
+ } else {
112
+ throw e ;
113
+ }
114
+ } finally {
115
+ fut . destroy ( ) ;
116
+ wrapped . destroy ( ) ;
117
+ }
118
+ }
119
+ term . resume ( ) ;
120
+ await sleep ( 10 ) ;
121
+ unlock ( ) ;
122
+ }
123
+
124
+ pyconsole . stdout_callback = ( s ) => echo ( s , { newline : false } ) ;
125
+ pyconsole . stderr_callback = ( s ) => {
126
+ term . error ( s . trimEnd ( ) ) ;
127
+ } ;
128
+
129
+ return interpreter ;
130
+ }
131
+
23
132
export const PlaygroundTerminal : React . FC = ( ) => {
24
133
const terminalRef = useRef ( null ) ;
25
134
26
- const { state} = useEnvironmentSetup ( ) ;
135
+ const { state, pyodide} = useEnvironmentSetup ( ) ;
136
+
137
+ const setupComplete = useRef < boolean > ( false ) ;
27
138
28
139
useEffect ( ( ) => {
29
- terminalRef . current . echo ( "Setting up environment" ) ;
30
- terminalRef . current . freeze ( ) ;
140
+ terminalRef . current . echo ( "Setting up environment..." ) ;
141
+ terminalRef . current . freeze ( true ) ;
142
+ terminalRef . current . setPrompt ( "" ) ;
31
143
} , [ ] )
32
144
33
145
useEffect ( ( ) => {
34
- if ( state . pyodideStatus !== "done unpacking" ) {
146
+ if ( state . environmentStatus === EnvironmentStatus . WaitBitbakeOrPyodide ) {
147
+ let s = "" ;
148
+ switch ( state . pyodideStatus ) {
149
+ case PyodideStatus . Idle :
150
+ s = "idle" ;
151
+ break ;
152
+ case PyodideStatus . Fetching :
153
+ s = "fetching..." ;
154
+ break ;
155
+ case PyodideStatus . Loading :
156
+ s = "loading..." ;
157
+ break ;
158
+ case PyodideStatus . Done :
159
+ s = "done!" ;
160
+ break ;
161
+ case PyodideStatus . Inactive :
162
+ s = "inactive" ;
163
+ break ;
164
+ }
165
+
35
166
terminalRef . current . setPrompt (
36
- `Downloading bitbake : ${ progress ( state . bitbakeProgress , 80 ) } %\nPyodide: ${ state . pyodideStatus } `
167
+ `Downloading BitBake : ${ progress ( state . bitbakeProgress , 30 ) } %\nPyodide: ${ s } `
37
168
)
38
169
} else {
39
- terminalRef . current . setPrompt (
40
- `Done unpacking BitBake`
41
- )
42
- }
43
- } , [ state ] ) ;
170
+ switch ( state . environmentStatus ) {
171
+ case EnvironmentStatus . UnpackingBitbake :
172
+ terminalRef . current . setPrompt (
173
+ `Unpacking BitBake...`
174
+ )
175
+ break ;
176
+ case EnvironmentStatus . LoadingSqlite3 :
177
+ terminalRef . current . setPrompt (
178
+ `Loading sqlite3...`
179
+ )
180
+ break ;
181
+ case EnvironmentStatus . Configuring :
182
+ terminalRef . current . setPrompt (
183
+ `Installing import hooks...`
184
+ )
185
+ break ;
186
+ case EnvironmentStatus . ImportingBitbake :
187
+ terminalRef . current . setPrompt (
188
+ `Importing BitBake...`
189
+ )
190
+ break ;
191
+ case EnvironmentStatus . Ready :
192
+ if ( ! setupComplete . current ) {
193
+ setupComplete . current = true ;
44
194
45
- const interpreter = ( command , term ) => {
195
+ terminalRef . current . setInterpreter ( create_interpreter ( pyodide , terminalRef . current ) ) ;
46
196
47
- } ;
197
+ terminalRef . current . echo ( "Ready :)\n" ) ;
198
+ terminalRef . current . setPrompt ( ">>> " ) ;
199
+ terminalRef . current . freeze ( false ) ;
200
+ }
201
+ break ;
202
+ }
203
+ }
204
+ } , [ pyodide , state ] ) ;
48
205
49
- return ( < JQueryTerminal interpreter = { interpreter } ref = { terminalRef } /> )
206
+ return ( < JQueryTerminal ref = { terminalRef } /> )
50
207
}
0 commit comments