@@ -20,6 +20,7 @@ class Python {
2020 $style = null ;
2121 #codes = [ ] ;
2222 #niddle = 0 ;
23+ #inputCount = 0 ;
2324
2425 async init ( $page , cacheFile , cacheFileUrl ) {
2526
@@ -67,6 +68,7 @@ class Python {
6768 className : 'print input' ,
6869 child : tag ( 'textarea' , {
6970 onkeydown : this . #onkeydown. bind ( this ) ,
71+ oninput : this . #oninput. bind ( this ) ,
7072 } ) ,
7173 } ) ;
7274
@@ -93,14 +95,7 @@ class Python {
9395 }
9496
9597 async run ( ) {
96- const $main = this . $page . get ( '.main' ) ;
97- if ( ! this . $page . isConnected ) {
98- this . $page . classList . remove ( 'hide' ) ;
99- this . $page . show ( ) ;
100- }
101-
102- $main . innerHTML = '' ;
103-
98+ this . #showPage( ) ;
10499 await this . #cacheFile. writeFile ( '' ) ;
105100 this . #append( this . $input ) ;
106101
@@ -116,6 +111,10 @@ class Python {
116111 await this . runCode ( code ) ;
117112 }
118113
114+ async terminal ( ) {
115+ this . #showPage( ) ;
116+ }
117+
119118 async runCode ( code ) {
120119 this . #worker. postMessage ( {
121120 action : 'run' ,
@@ -168,6 +167,20 @@ class Python {
168167 this . #append( $output , this . $input ) ;
169168 }
170169
170+ #showPage( ) {
171+ const $main = this . $page . get ( '.main' ) ;
172+ if ( ! this . $page . isConnected ) {
173+ this . $page . classList . remove ( 'hide' ) ;
174+ this . $page . show ( ) ;
175+ }
176+ $main . innerHTML = '' ;
177+ }
178+
179+ #clearConsole( ) {
180+ this . $page . get ( '.main' ) . innerHTML = '' ;
181+ this . #append( this . $input ) ;
182+ }
183+
171184 #append( ...$el ) {
172185 const $main = this . $page . get ( '.main' ) ;
173186 if ( ! $main ) this . $page . append ( tag ( 'div' , { className : 'main' } ) ) ;
@@ -210,24 +223,12 @@ class Python {
210223 }
211224
212225 #onkeydown( e ) {
213- if ( e . key === 'Enter' ) {
214- e . preventDefault ( ) ;
215- const value = e . target . value ;
216- this . print ( value , 'input' ) ;
217- if ( this . #isInput) {
218- this . #isInput = false ;
219- this . #cacheFile. writeFile ( value + '\0' ) ;
220- } else {
221- this . #codes. push ( value ) ;
222- this . #niddle = this . #codes. length ;
223- this . runCode ( value ) ;
224- }
225- e . target . value = '' ;
226- return ;
227- }
228-
226+ const value = e . target . value ;
227+ const lines = value . split ( '\n' ) ;
228+ const canGoUp = this . #getCursorPosition( ) === 1 ;
229+ const canGoDown = this . #getCursorPosition( ) === lines . length ;
229230 // if up arrow is pressed, show previous code
230- if ( e . key === 'ArrowUp' ) {
231+ if ( canGoUp && e . key === 'ArrowUp' ) {
231232 e . preventDefault ( ) ;
232233 if ( this . #niddle > 0 ) {
233234 this . #niddle -= 1 ;
@@ -236,29 +237,100 @@ class Python {
236237 }
237238
238239 // if down arrow is pressed, show next code
239- if ( e . key === 'ArrowDown' ) {
240+ if ( canGoDown && e . key === 'ArrowDown' ) {
240241 e . preventDefault ( ) ;
241242 if ( this . #niddle < this . #codes. length ) {
242243 this . #niddle += 1 ;
243244 e . target . value = this . #codes[ this . #niddle] || '' ;
244245 }
245246 }
247+
248+ // if ctrl + l is pressed, clear the input
249+ if ( e . key === 'l' && e . ctrlKey ) {
250+ this . #clearConsole( ) ;
251+ }
252+
253+ if ( e . key === 'Tab' ) {
254+ e . preventDefault ( ) ;
255+ e . target . value += '\t' ;
256+ }
246257 }
247- }
248258
259+ #getCursorPosition( ) {
260+ const $textarea = this . $input . get ( 'textarea' ) ;
261+ const {
262+ selectionStart,
263+ selectionEnd,
264+ } = $textarea ;
265+
266+ if ( selectionStart !== selectionEnd ) return ;
267+ const lines = $textarea . value ;
249268
250- console . log ( 'Python plugin' ) ;
269+ // get the line number of the cursor
270+ return lines . slice ( 0 , selectionStart ) . split ( '\n' ) . length ;
271+ }
272+
273+ #oninput( e ) {
274+ const $el = e . target ;
275+ let { value } = $el ;
276+ $el . style . height = `${ $el . scrollHeight } px` ;
277+ // check if new line is added
278+ if ( value . endsWith ( '\n' ) ) {
279+ if ( this . #isInput) {
280+ this . #isInput = false ;
281+ value = value . slice ( 0 , - 1 ) ;
282+ this . #cacheFile. writeFile ( value + `\0${ this . #inputCount++ } ` ) ;
283+ this . print ( value , 'input' ) ;
284+ this . $input . get ( 'textarea' ) . value = '' ;
285+ return ;
286+ }
287+
288+ if ( ! this . #isIncomplete( value ) ) {
289+ this . #codes. push ( value . trim ( ) ) ;
290+ this . #niddle = this . #codes. length ;
291+ this . print ( value , 'input' ) ;
292+ this . runCode ( value ) ;
293+ this . $input . get ( 'textarea' ) . value = '' ;
294+ }
295+ }
296+ }
297+
298+ #isIncomplete( code ) {
299+ const lines = code . trim ( ) . split ( '\n' ) ;
300+ let lastLine = lines [ lines . length - 1 ] ;
301+
302+ // if last line ends with ':', it is incomplete
303+ if ( / : $ / . test ( lastLine ) ) {
304+ return true ;
305+ }
306+
307+ // if last line starts with tab or soft tab, it is incomplete
308+ if ( / ^ \W + / . test ( lastLine ) ) {
309+ if ( / \n \n $ / . test ( code ) ) {
310+ return false ;
311+ }
312+ return true ;
313+ }
314+
315+ return false ;
316+ }
317+ }
251318
252319if ( window . acode ) {
253320 const python = new Python ( ) ;
254321 acode . setPluginInit ( 'acode.plugin.python' , ( baseUrl , $page , { cacheFileUrl, cacheFile } ) => {
255322 if ( ! baseUrl . endsWith ( '/' ) ) baseUrl += '/' ;
256323 python . baseUrl = baseUrl ;
257324 python . init ( $page , cacheFile , cacheFileUrl ) ;
258- console . log ( 'Python plugin initialized' ) ;
259325 } ) ;
260326 acode . setPluginUnmount ( 'acode.plugin.python' , ( ) => {
261327 python . destroy ( ) ;
262- console . log ( 'Python plugin unmounted' ) ;
263- } )
328+ } ) ;
329+ // future reference
330+ if ( acode . registerShortcut ) {
331+ acode . registerShortcut ( 'Python Console' , python . terminal . bind ( python ) , 'Python' ) ;
332+ }
333+ if ( acode . registerMenu ) {
334+ acode . registerMenu ( 'Python Console' , python . terminal . bind ( python ) , 'Python' ) ;
335+ }
264336}
0 commit comments