@@ -11,78 +11,206 @@ hyperbook.python = (function () {
1111 `${ HYPERBOOK_ASSETS } directive-pyide/webworker.js`
1212 ) ;
1313
14- const callbacks = { } ;
15- let isRunning = false ;
14+ let callback = null ;
15+ /**
16+ * @type Uint8Array
17+ */
18+ let interruptBuffer ;
19+ /**
20+ * @type Int32Array
21+ */
22+ let stdinBuffer ;
23+ if ( window . crossOriginIsolated ) {
24+ interruptBuffer = new Uint8Array ( new SharedArrayBuffer ( 1 ) ) ;
25+ stdinBuffer = new Int32Array ( new SharedArrayBuffer ( 1024 ) ) ;
26+ pyodideWorker . postMessage ( {
27+ type : "setStdinBuffer" ,
28+ payload : { stdinBuffer } ,
29+ } ) ;
30+ pyodideWorker . postMessage ( {
31+ type : "setInterruptBuffer" ,
32+ payload : { interruptBuffer } ,
33+ } ) ;
34+ } else {
35+ interruptBuffer = new ArrayBuffer ( 1 ) ;
36+ pyodideWorker . postMessage ( {
37+ type : "setInterruptBuffer" ,
38+ payload : { interruptBuffer } ,
39+ } ) ;
40+ }
1641
17- const asyncRun = ( id ) => {
18- if ( isRunning ) return ;
42+ const asyncRun = ( id , type ) => {
43+ if ( callback ) return ;
1944
20- isRunning = true ;
21- updateRunning ( ) ;
45+ interruptBuffer [ 0 ] = 0 ;
2246 return ( script , context ) => {
23- // the id could be generated more carefully
2447 return new Promise ( ( onSuccess ) => {
25- callbacks [ id ] = onSuccess ;
48+ callback = onSuccess ;
49+ updateRunning ( id , type ) ;
2650 pyodideWorker . postMessage ( {
27- ...context ,
28- python : script ,
51+ type : "run" ,
52+ payload : {
53+ ...context ,
54+ python : script ,
55+ } ,
2956 id,
3057 } ) ;
3158 } ) ;
3259 } ;
3360 } ;
3461
35- const updateRunning = ( ) => {
62+ function interruptExecution ( ) {
63+ // 2 stands for SIGINT.
64+ interruptBuffer [ 0 ] = 2 ;
65+ }
66+
67+ const updateRunning = ( id , type ) => {
3668 for ( let elem of elems ) {
3769 const run = elem . getElementsByClassName ( "run" ) [ 0 ] ;
38- if ( isRunning ) {
39- run . classList . add ( "running" ) ;
40- run . textContent = "Running ..." ;
70+ const test = elem . getElementsByClassName ( "test" ) [ 0 ] ;
71+ if ( callback ) {
72+ if ( elem . id === id && type === "run" ) {
73+ run . textContent = "Running (Click to stop) ..." ;
74+ run . addEventListener ( "click" , interruptExecution ) ;
75+ } else if ( test && elem . id === id && type === "test" ) {
76+ test . textContent = "Testing (Click to stop) ..." ;
77+ test . addEventListener ( "click" , interruptExecution ) ;
78+ } else {
79+ run . classList . add ( "running" ) ;
80+ run . disabled = true ;
81+ if ( test ) {
82+ test . classList . add ( "running" ) ;
83+ test . disabled = true ;
84+ }
85+ }
4186 } else {
4287 run . classList . remove ( "running" ) ;
4388 run . textContent = "Run" ;
89+ run . disabled = false ;
90+ run . removeEventListener ( "click" , interruptExecution ) ;
91+ if ( test ) {
92+ test . classList . remove ( "running" ) ;
93+ test . textContent = "Test" ;
94+ test . disabled = false ;
95+ test . removeEventListener ( "click" , interruptExecution ) ;
96+ }
4497 }
45- run . disabled = isRunning ;
4698 }
4799 } ;
48100
49101 pyodideWorker . onmessage = ( event ) => {
50- const { id, ...data } = event . data ;
51- if ( data . type === "stdout" ) {
52- const output = document
53- . getElementById ( id )
54- . getElementsByClassName ( "output" ) [ 0 ] ;
55- output . appendChild ( document . createTextNode ( data . message + "\n" ) ) ;
56- return ;
102+ const { id, type, payload } = event . data ;
103+ switch ( type ) {
104+ case "stdout" : {
105+ const output = document
106+ . getElementById ( id )
107+ . getElementsByClassName ( "output" ) [ 0 ] ;
108+ output . appendChild ( document . createTextNode ( payload + "\n" ) ) ;
109+ break ;
110+ }
111+ case "error" : {
112+ const onSuccess = callback ;
113+ onSuccess ( { error : payload } ) ;
114+ break ;
115+ }
116+ case "success" : {
117+ const onSuccess = callback ;
118+ onSuccess ( { results : payload } ) ;
119+ break ;
120+ }
57121 }
58- const onSuccess = callbacks [ id ] ;
59- delete callbacks [ id ] ;
60- isRunning = false ;
61- updateRunning ( ) ;
62- onSuccess ( data ) ;
63122 } ;
64123
65124 const elems = document . getElementsByClassName ( "directive-pyide" ) ;
66125
67126 for ( let elem of elems ) {
68127 const editor = elem . getElementsByClassName ( "editor" ) [ 0 ] ;
69128 const run = elem . getElementsByClassName ( "run" ) [ 0 ] ;
129+ const test = elem . getElementsByClassName ( "test" ) [ 0 ] ;
70130 const output = elem . getElementsByClassName ( "output" ) [ 0 ] ;
131+ const input = elem . getElementsByClassName ( "input" ) [ 0 ] ;
132+ const outputBtn = elem . getElementsByClassName ( "output-btn" ) [ 0 ] ;
133+ const inputBtn = elem . getElementsByClassName ( "input-btn" ) [ 0 ] ;
71134 const id = elem . id ;
135+ let tests = [ ] ;
136+ try {
137+ tests = JSON . parse ( atob ( elem . getAttribute ( "data-tests" ) ) ) ;
138+ } catch ( e ) { }
139+
140+ function showInput ( ) {
141+ outputBtn . classList . remove ( "active" ) ;
142+ inputBtn . classList . add ( "active" ) ;
143+ output . classList . add ( "hidden" ) ;
144+ input . classList . remove ( "hidden" ) ;
145+ }
146+ function showOutput ( ) {
147+ outputBtn . classList . add ( "active" ) ;
148+ inputBtn . classList . remove ( "active" ) ;
149+ output . classList . remove ( "hidden" ) ;
150+ input . classList . add ( "hidden" ) ;
151+ }
152+
153+ outputBtn ?. addEventListener ( "click" , showOutput ) ;
154+ inputBtn ?. addEventListener ( "click" , showInput ) ;
155+
156+ test ?. addEventListener ( "click" , async ( ) => {
157+ showOutput ( ) ;
158+ if ( callback ) return ;
159+
160+ output . innerHTML = "" ;
161+
162+ const script = editor . value ;
163+ for ( let test of tests ) {
164+ const testCode = test . code . replace ( "#SCRIPT#" , script ) ;
165+
166+ const heading = document . createElement ( "div" ) ;
167+ console . log ( test ) ;
168+ heading . innerHTML = `== Test ${ test . name } ==` ;
169+ heading . classList . add ( "test-heading" ) ;
170+ output . appendChild ( heading ) ;
171+
172+ await asyncRun ( id , "test" ) ( testCode , { } )
173+ . then ( ( { results, error } ) => {
174+ if ( results ) {
175+ output . textContent += results ;
176+ } else if ( error ) {
177+ output . textContent += error ;
178+ }
179+ callback = null ;
180+ updateRunning ( id , "test" ) ;
181+ } )
182+ . catch ( ( e ) => {
183+ output . textContent = `Error: ${ e } ` ;
184+ console . log ( e ) ;
185+ callback = null ;
186+ updateRunning ( id , "test" ) ;
187+ } ) ;
188+ }
189+ } ) ;
190+
191+ run ?. addEventListener ( "click" , async ( ) => {
192+ showOutput ( ) ;
193+ if ( callback ) return ;
72194
73- run ?. addEventListener ( "click" , ( ) => {
74195 const script = editor . value ;
75196 output . innerHTML = "" ;
76- asyncRun ( id ) ( script , { } )
197+ asyncRun ( id , "run" ) ( script , {
198+ inputs : input . value . split ( "\n" ) ,
199+ } )
77200 . then ( ( { results, error } ) => {
78201 if ( results ) {
79- output . textContent = results ;
202+ output . textContent + = results ;
80203 } else if ( error ) {
81- output . textContent = error ;
204+ output . textContent + = error ;
82205 }
206+ callback = null ;
207+ updateRunning ( id , "run" ) ;
83208 } )
84209 . catch ( ( e ) => {
85210 output . textContent = `Error: ${ e } ` ;
211+ console . log ( e ) ;
212+ callback = null ;
213+ updateRunning ( id , "run" ) ;
86214 } ) ;
87215 } ) ;
88216 }
0 commit comments