1+ import { CodeJar } from 'https://esm.sh/codejar@4' ;
2+
13const SZ = 1 << 20 ;
24const DEV = ! [ 'demo.edgepython.com' ] . includes ( location . hostname ) ;
35const WASM_SOURCES = DEV
@@ -12,6 +14,53 @@ const MAX_LINES = 99;
1214const $ = ( id ) => document . getElementById ( id ) ;
1315const [ ed , ln , btn , term , statusEl ] = [ 'ed' , 'ln' , 'run' , 'term' , 'status' ] . map ( $ ) ;
1416
17+ const KW = new Set ( [
18+ 'as' , 'if' , 'in' , 'is' , 'or' ,
19+ 'and' , 'def' , 'del' , 'for' , 'not' , 'try' ,
20+ 'case' , 'elif' , 'else' , 'from' , 'pass' , 'type' , 'with' ,
21+ 'async' , 'await' , 'break' , 'class' , 'match' , 'raise' , 'while' , 'yield' ,
22+ 'assert' , 'except' , 'global' , 'import' , 'lambda' , 'return' ,
23+ 'finally' ,
24+ 'continue' , 'nonlocal'
25+ ] ) ;
26+ const BI = new Set ( [
27+ 'print' , 'len' , 'range' , 'int' , 'str' , 'float' , 'list' , 'dict' , 'tuple' , 'set' ,
28+ 'bool' , 'isinstance' , 'issubclass' , 'enumerate' , 'zip' , 'map' , 'filter' ,
29+ 'abs' , 'min' , 'max' , 'sum' , 'round' , 'pow' , 'divmod' , 'hash' , 'id' , 'repr' ,
30+ 'ord' , 'chr' , 'hex' , 'oct' , 'bin' , 'open' , 'input' , 'iter' , 'next' , 'reversed' , 'sorted' ,
31+ 'any' , 'all' , 'format' , 'frozenset' , 'bytearray' , 'bytes' , 'complex' , 'memoryview' ,
32+ 'object' , 'property' , 'staticmethod' , 'classmethod' , 'super' , 'slice' ,
33+ 'callable' , 'getattr' , 'setattr' , 'hasattr' , 'delattr' , 'dir' , 'vars' , 'globals' , 'locals' ,
34+ 'NotImplemented' , 'Ellipsis' , 'self' , 'cls'
35+ ] ) ;
36+ const LIT = new Set ( [ 'True' , 'False' , 'None' ] ) ;
37+
38+ const TOKEN_RE = / ( # [ ^ \n ] * ) | ( (?: \b [ f F r R b B u U ] { 1 , 2 } ) ? (?: " " " [ \s \S ] * ?" " " | ' ' ' [ \s \S ] * ?' ' ' | " (?: \\ .| [ ^ " \\ \n ] ) * " | ' (?: \\ .| [ ^ ' \\ \n ] ) * ' ) ) | ( 0 [ x X ] [ \d a - f A - F _ ] + | 0 [ o O ] [ 0 - 7 _ ] + | 0 [ b B ] [ 0 1 _ ] + | \d [ \d _ ] * (?: \. [ \d _ ] * ) ? (?: [ e E ] [ + - ] ? \d + ) ? [ j J ] ? | \. \d [ \d _ ] * (?: [ e E ] [ + - ] ? \d + ) ? [ j J ] ? ) | ( [ A - Z a - z _ ] \w * ) / g;
39+
40+ const esc = ( s ) => s . replace ( / [ & < > ] / g, ( c ) => ( { '&' : '&' , '<' : '<' , '>' : '>' } [ c ] ) ) ;
41+
42+ const highlight = ( src ) => esc ( src ) . replace ( TOKEN_RE , ( m , com , str , num , word ) => {
43+ if ( com ) return `<span class="tk-com">${ com } </span>` ;
44+ if ( str ) return `<span class="tk-str">${ str } </span>` ;
45+ if ( num ) return `<span class="tk-num">${ num } </span>` ;
46+ if ( word ) {
47+ if ( KW . has ( word ) ) return `<span class="tk-kw">${ word } </span>` ;
48+ if ( LIT . has ( word ) ) return `<span class="tk-lit">${ word } </span>` ;
49+ if ( BI . has ( word ) ) return `<span class="tk-bi">${ word } </span>` ;
50+ return word ;
51+ }
52+ return m ;
53+ } ) ;
54+
55+ const jar = CodeJar ( ed , ( editor ) => {
56+ editor . innerHTML = highlight ( editor . textContent ) ;
57+ } , {
58+ tab : ' ' ,
59+ indentOn : / : \s * $ / ,
60+ spellcheck : false ,
61+ addClosing : false ,
62+ } ) ;
63+
1564let wasm ;
1665
1766const fmt = ( ms ) => ms < 1000 ? `${ ms . toFixed ( 0 ) } ms` : `${ ( ms / 1000 ) . toFixed ( 2 ) } s` ;
@@ -43,13 +92,13 @@ const loadWasm = async () => {
4392
4493const runCode = async ( ) => {
4594 if ( ! wasm ) return ;
46- const srcBytes = new TextEncoder ( ) . encode ( ed . value ) ;
95+ const srcBytes = new TextEncoder ( ) . encode ( jar . toString ( ) ) ;
4796 if ( srcBytes . length > SZ ) return void ( term . textContent = `Error: Source exceeds ${ SZ } bytes` ) ;
4897
4998 setStatus ( 'Running...' , CLS . ok ) ;
5099 btn . disabled = true ;
51100
52- await new Promise ( resolve => setTimeout ( resolve , 10 ) ) ;
101+ await new Promise ( r => requestAnimationFrame ( ( ) => requestAnimationFrame ( r ) ) ) ;
53102
54103 const [ out , t ] = await time ( ( ) => {
55104 new Uint8Array ( wasm . memory . buffer ) . set ( srcBytes , wasm . src_ptr ( ) ) ;
@@ -63,21 +112,48 @@ const runCode = async () => {
63112} ;
64113
65114const sync = ( ) => {
66- const lines = ed . value . split ( '\n' ) ;
67- if ( lines . length > MAX_LINES ) ed . value = lines . slice ( 0 , MAX_LINES ) . join ( '\n' ) ;
68- const n = Math . min ( ed . value . split ( '\n' ) . length , MAX_LINES ) ;
115+ const text = jar . toString ( ) . replace ( / \n $ / , '' ) ;
116+ const n = Math . max ( 1 , Math . min ( text . split ( '\n' ) . length , MAX_LINES ) ) ;
69117 ln . textContent = Array . from ( { length : n } , ( _ , i ) => String ( i + 1 ) . padStart ( 2 , '0' ) ) . join ( '\n' ) ;
70118 ln . scrollTop = ed . scrollTop ;
71119} ;
72120
73121btn . addEventListener ( 'click' , runCode ) ;
122+
74123ed . addEventListener ( 'keydown' , ( e ) => {
75- if ( ( e . ctrlKey || e . metaKey ) && e . key === 'Enter' ) { e . preventDefault ( ) ; runCode ( ) ; }
76- else if ( e . key === 'Enter' && ed . value . split ( '\n' ) . length >= MAX_LINES ) e . preventDefault ( ) ;
77- } ) ;
78- ed . oninput = ed . onscroll = sync ;
124+ if ( ( e . ctrlKey || e . metaKey ) && e . key === 'Enter' ) {
125+ e . preventDefault ( ) ;
126+ e . stopPropagation ( ) ;
127+ runCode ( ) ;
128+ return ;
129+ }
130+ if ( e . key === 'Enter' && jar . toString ( ) . split ( '\n' ) . length >= MAX_LINES ) {
131+ e . preventDefault ( ) ;
132+ e . stopPropagation ( ) ;
133+ return ;
134+ }
135+ if ( e . key === 'Backspace' ) {
136+ const pos = jar . save ( ) ;
137+ if ( pos . start !== pos . end ) return ;
138+ const caret = pos . start ;
139+ if ( caret === 0 ) return ;
140+ const text = jar . toString ( ) ;
141+ const lineStart = text . lastIndexOf ( '\n' , caret - 1 ) + 1 ;
142+ const before = text . slice ( lineStart , caret ) ;
143+ if ( before . length === 0 || ! / ^ [ \t ] + $ / . test ( before ) ) return ;
144+ e . preventDefault ( ) ;
145+ e . stopPropagation ( ) ;
146+ const TAB = 4 ;
147+ const prevStop = Math . floor ( ( before . length - 1 ) / TAB ) * TAB ;
148+ const del = before . length - prevStop ;
149+ jar . restore ( { start : caret - del , end : caret } ) ;
150+ document . execCommand ( 'delete' ) ;
151+ }
152+ } , true ) ;
153+
154+ ed . addEventListener ( 'scroll' , ( ) => { ln . scrollTop = ed . scrollTop ; } ) ;
79155
80- ed . value = DEFAULT_CODE ;
156+ jar . onUpdate ( sync ) ;
157+ jar . updateCode ( DEFAULT_CODE ) ;
81158
82- sync ( ) ;
83159loadWasm ( ) ;
0 commit comments