├── LICENCE ├── README.md ├── lib └── microWebSrv.py ├── media └── interface.png └── weditor ├── index.html ├── js ├── script.js ├── term.min.js └── webrepl.min.js ├── start.py └── style.css /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 vsolina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MicroPython web code editor / IDE 2 | ================================= 3 | 4 | Web-based code editor for WiFi + MicroPython enabled microcontrollers 5 | 6 | Uses [Cloud9 Ace](https://ace.c9.io/) editor for code editing 7 | 8 | Has an integrated WebREPL console 9 | 10 | Developed and tested on ESP32, but should work on other boards too 11 | 12 | 13 | ![MicroPython Web IDE](media/interface.png) 14 | 15 | Installation 16 | ------------ 17 | 18 | 1. Flash a recent version (1.17+) of MicroPython to your Controller (http://micropython.org/download) 19 | 2. Copy contents of /weditor and lib/microWebSrv.py to the root of Controller's file system 20 | 21 | ``` 22 | $ cd micropython-web-editor 23 | $ ampy --port /dev/ttyUSB0 put weditor /weditor 24 | $ ampy --port /dev/ttyUSB0 put lib /lib 25 | ``` 26 | 27 | 3. Setup WebREPL (optional) 28 | On your Controller run: 29 | ``` 30 | import webrepl_setup 31 | ``` 32 | 33 | 4. Update boot.py 34 | 35 | ```python 36 | print("Activating network") 37 | import network 38 | wlan = network.WLAN(network.STA_IF) 39 | wlan.active(True) 40 | wlan.connect('YOUR_SSID', 'YOUR_PASS') 41 | 42 | print("Starting webrepl") 43 | import webrepl 44 | webrepl.start() 45 | 46 | print("Starting IDE web service") 47 | import weditor.start 48 | ``` 49 | 50 | 5. Reboot Controller 51 | 6. Open controller's IP in your browser (make sure to use http:// scheme - https not supported) 52 | 7. Have a nice day 53 | 54 | Install shell for MicroPython (optional) 55 | ---------------------------------------- 56 | For easier file system operations (mv, rm, mkdir, df, etc.) 57 | [MicroPython shell](https://github.com/vsolina/mipyshell) - instructions on this page 58 | 59 | 60 | Bundled open-source projects 61 | ---------------------------- 62 | 63 | *This project would not be possible without these exceptional projects* 64 | 65 | [Cloud9 Ace code editor](https://github.com/ajaxorg/ace) 66 | 67 | [MicroWebSrv](https://github.com/jczic/MicroWebSrv/) 68 | 69 | [MicroPython's WebREPL](https://github.com/micropython/webrepl) 70 | 71 | [And most importantly: MicroPython](https://github.com/micropython) 72 | 73 | 74 | Notes 75 | ----- 76 | 77 | * File saving does not work well with large files yet 78 | * This editor was mostly written and debugged using itself 79 | * esp8266 does not have enough RAM to run all required components (yet) 80 | * When using the IDE, Internet connection is required because Ace is hosted on cdnjs (to reduce storage requirements); but it's possible to bundle required ace.js files on Controller's flash (raise an Issue and I'll explain how to do it) 81 | -------------------------------------------------------------------------------- /lib/microWebSrv.py: -------------------------------------------------------------------------------- 1 | """ 2 | The MIT License (MIT) 3 | Copyright © 2018 Jean-Christophe Bos & HC² (www.hc2.fr) 4 | """ 5 | 6 | 7 | from json import loads, dumps 8 | from os import stat 9 | from _thread import start_new_thread 10 | import socket 11 | import gc 12 | import re 13 | 14 | try : 15 | from microWebTemplate import MicroWebTemplate 16 | except : 17 | pass 18 | 19 | try : 20 | from microWebSocket import MicroWebSocket 21 | except : 22 | pass 23 | 24 | class MicroWebSrvRoute : 25 | def __init__(self, route, method, func, routeArgNames, routeRegex) : 26 | self.route = route 27 | self.method = method 28 | self.func = func 29 | self.routeArgNames = routeArgNames 30 | self.routeRegex = routeRegex 31 | 32 | 33 | class MicroWebSrv : 34 | 35 | # ============================================================================ 36 | # ===( Constants )============================================================ 37 | # ============================================================================ 38 | 39 | _indexPages = [ 40 | "index.pyhtml", 41 | "index.html", 42 | "index.htm", 43 | "default.pyhtml", 44 | "default.html", 45 | "default.htm" 46 | ] 47 | 48 | _mimeTypes = { 49 | ".txt" : "text/plain", 50 | ".htm" : "text/html", 51 | ".html" : "text/html", 52 | ".css" : "text/css", 53 | ".csv" : "text/csv", 54 | ".js" : "application/javascript", 55 | ".xml" : "application/xml", 56 | ".xhtml" : "application/xhtml+xml", 57 | ".json" : "application/json", 58 | ".zip" : "application/zip", 59 | ".pdf" : "application/pdf", 60 | ".jpg" : "image/jpeg", 61 | ".jpeg" : "image/jpeg", 62 | ".png" : "image/png", 63 | ".gif" : "image/gif", 64 | ".svg" : "image/svg+xml", 65 | ".ico" : "image/x-icon" 66 | } 67 | 68 | _html_escape_chars = { 69 | "&" : "&", 70 | '"' : """, 71 | "'" : "'", 72 | ">" : ">", 73 | "<" : "<" 74 | } 75 | 76 | _pyhtmlPagesExt = '.pyhtml' 77 | 78 | # ============================================================================ 79 | # ===( Class globals )======================================================= 80 | # ============================================================================ 81 | 82 | _docoratedRouteHandlers = [] 83 | 84 | # ============================================================================ 85 | # ===( Utils )=============================================================== 86 | # ============================================================================ 87 | 88 | @classmethod 89 | def route(cls, url, method='GET'): 90 | """ Adds a route handler function to the routing list """ 91 | def route_decorator(func): 92 | item = (url, method, func) 93 | cls._docoratedRouteHandlers.append(item) 94 | return func 95 | return route_decorator 96 | 97 | # ---------------------------------------------------------------------------- 98 | 99 | @staticmethod 100 | def HTMLEscape(s) : 101 | return ''.join(MicroWebSrv._html_escape_chars.get(c, c) for c in s) 102 | 103 | # ---------------------------------------------------------------------------- 104 | 105 | @staticmethod 106 | def _tryAllocByteArray(size) : 107 | for x in range(10) : 108 | try : 109 | gc.collect() 110 | return bytearray(size) 111 | except : 112 | pass 113 | return None 114 | 115 | # ---------------------------------------------------------------------------- 116 | 117 | @staticmethod 118 | def _tryStartThread(func, args=()) : 119 | for x in range(10) : 120 | try : 121 | gc.collect() 122 | start_new_thread(func, args) 123 | return True 124 | except : 125 | global _mwsrv_thread_id 126 | try : 127 | _mwsrv_thread_id += 1 128 | except : 129 | _mwsrv_thread_id = 0 130 | try : 131 | start_new_thread('MWSRV_THREAD_%s' % _mwsrv_thread_id, func, args) 132 | return True 133 | except : 134 | pass 135 | return False 136 | 137 | # ---------------------------------------------------------------------------- 138 | 139 | @staticmethod 140 | def _unquote(s) : 141 | r = s.split('%') 142 | for i in range(1, len(r)) : 143 | s = r[i] 144 | try : 145 | r[i] = chr(int(s[:2], 16)) + s[2:] 146 | except : 147 | r[i] = '%' + s 148 | return ''.join(r) 149 | 150 | # ---------------------------------------------------------------------------- 151 | 152 | @staticmethod 153 | def _unquote_plus(s) : 154 | return MicroWebSrv._unquote(s.replace('+', ' ')) 155 | 156 | # ---------------------------------------------------------------------------- 157 | 158 | @staticmethod 159 | def _fileExists(path) : 160 | try : 161 | stat(path) 162 | return True 163 | except : 164 | return False 165 | 166 | # ---------------------------------------------------------------------------- 167 | 168 | @staticmethod 169 | def _isPyHTMLFile(filename) : 170 | return filename.lower().endswith(MicroWebSrv._pyhtmlPagesExt) 171 | 172 | # ============================================================================ 173 | # ===( Constructor )========================================================== 174 | # ============================================================================ 175 | 176 | def __init__( self, 177 | routeHandlers = [], 178 | port = 80, 179 | bindIP = '0.0.0.0', 180 | webPath = "/flash/www" ) : 181 | 182 | self._srvAddr = (bindIP, port) 183 | self._webPath = webPath 184 | self._notFoundUrl = None 185 | self._started = False 186 | 187 | self.MaxWebSocketRecvLen = 1024 188 | self.WebSocketThreaded = True 189 | self.AcceptWebSocketCallback = None 190 | 191 | self._routeHandlers = [] 192 | routeHandlers += self._docoratedRouteHandlers 193 | for route, method, func in routeHandlers : 194 | routeParts = route.split('/') 195 | # -> ['', 'users', '', 'addresses', '', 'test', ''] 196 | routeArgNames = [] 197 | routeRegex = '' 198 | for s in routeParts : 199 | if s.startswith('<') and s.endswith('>') : 200 | routeArgNames.append(s[1:-1]) 201 | routeRegex += '/(\\w*)' 202 | elif s : 203 | routeRegex += '/' + s 204 | routeRegex += '$' 205 | # -> '/users/(\w*)/addresses/(\w*)/test/(\w*)$' 206 | routeRegex = re.compile(routeRegex) 207 | 208 | self._routeHandlers.append(MicroWebSrvRoute(route, method, func, routeArgNames, routeRegex)) 209 | 210 | # ============================================================================ 211 | # ===( Server Process )======================================================= 212 | # ============================================================================ 213 | 214 | def _serverProcess(self) : 215 | self._started = True 216 | while True : 217 | try : 218 | client, cliAddr = self._server.accept() 219 | except : 220 | break 221 | self._client(self, client, cliAddr) 222 | self._started = False 223 | 224 | # ============================================================================ 225 | # ===( Functions )============================================================ 226 | # ============================================================================ 227 | 228 | def Start(self, threaded=True) : 229 | if not self._started : 230 | self._server = socket.socket( socket.AF_INET, 231 | socket.SOCK_STREAM, 232 | socket.IPPROTO_TCP ) 233 | self._server.setsockopt( socket.SOL_SOCKET, 234 | socket.SO_REUSEADDR, 235 | 1 ) 236 | self._server.bind(self._srvAddr) 237 | self._server.listen(1) 238 | if threaded : 239 | MicroWebSrv._tryStartThread(self._serverProcess) 240 | else : 241 | self._serverProcess() 242 | 243 | # ---------------------------------------------------------------------------- 244 | 245 | def Stop(self) : 246 | if self._started : 247 | self._server.close() 248 | 249 | # ---------------------------------------------------------------------------- 250 | 251 | def IsStarted(self) : 252 | return self._started 253 | 254 | # ---------------------------------------------------------------------------- 255 | 256 | def SetNotFoundPageUrl(self, url=None) : 257 | self._notFoundUrl = url 258 | 259 | # ---------------------------------------------------------------------------- 260 | 261 | def GetMimeTypeFromFilename(self, filename) : 262 | filename = filename.lower() 263 | for ext in self._mimeTypes : 264 | if filename.endswith(ext) : 265 | return self._mimeTypes[ext] 266 | return None 267 | 268 | # ---------------------------------------------------------------------------- 269 | 270 | def GetRouteHandler(self, resUrl, method) : 271 | if self._routeHandlers : 272 | #resUrl = resUrl.upper() 273 | if resUrl.endswith('/') : 274 | resUrl = resUrl[:-1] 275 | method = method.upper() 276 | for rh in self._routeHandlers : 277 | if rh.method == method : 278 | m = rh.routeRegex.match(resUrl) 279 | if m : # found matching route? 280 | if rh.routeArgNames : 281 | routeArgs = {} 282 | for i, name in enumerate(rh.routeArgNames) : 283 | value = m.group(i+1) 284 | try : 285 | value = int(value) 286 | except : 287 | pass 288 | routeArgs[name] = value 289 | return (rh.func, routeArgs) 290 | else : 291 | return (rh.func, None) 292 | return (None, None) 293 | 294 | # ---------------------------------------------------------------------------- 295 | 296 | def _physPathFromURLPath(self, urlPath) : 297 | if urlPath == '/' : 298 | for idxPage in self._indexPages : 299 | physPath = self._webPath + '/' + idxPage 300 | if MicroWebSrv._fileExists(physPath) : 301 | return physPath 302 | else : 303 | physPath = self._webPath + urlPath 304 | if MicroWebSrv._fileExists(physPath) : 305 | return physPath 306 | return None 307 | 308 | # ============================================================================ 309 | # ===( Class Client )======================================================== 310 | # ============================================================================ 311 | 312 | class _client : 313 | 314 | # ------------------------------------------------------------------------ 315 | 316 | def __init__(self, microWebSrv, socket, addr) : 317 | socket.settimeout(2) 318 | self._microWebSrv = microWebSrv 319 | self._socket = socket 320 | self._addr = addr 321 | self._method = None 322 | self._path = None 323 | self._httpVer = None 324 | self._resPath = "/" 325 | self._queryString = "" 326 | self._queryParams = { } 327 | self._headers = { } 328 | self._contentType = None 329 | self._contentLength = 0 330 | 331 | if hasattr(socket, 'readline'): # MicroPython 332 | self._socketfile = self._socket 333 | else: # CPython 334 | self._socketfile = self._socket.makefile('rwb') 335 | 336 | self._processRequest() 337 | 338 | # ------------------------------------------------------------------------ 339 | 340 | def _processRequest(self) : 341 | try : 342 | response = MicroWebSrv._response(self) 343 | if self._parseFirstLine(response) : 344 | if self._parseHeader(response) : 345 | upg = self._getConnUpgrade() 346 | if not upg : 347 | routeHandler, routeArgs = self._microWebSrv.GetRouteHandler(self._resPath, self._method) 348 | if routeHandler : 349 | if routeArgs is not None: 350 | routeHandler(self, response, routeArgs) 351 | else: 352 | routeHandler(self, response) 353 | elif self._method.upper() == "GET" : 354 | filepath = self._microWebSrv._physPathFromURLPath(self._resPath) 355 | if filepath : 356 | if MicroWebSrv._isPyHTMLFile(filepath) : 357 | response.WriteResponsePyHTMLFile(filepath) 358 | else : 359 | contentType = self._microWebSrv.GetMimeTypeFromFilename(filepath) 360 | if contentType : 361 | response.WriteResponseFile(filepath, contentType) 362 | else : 363 | response.WriteResponseForbidden() 364 | else : 365 | response.WriteResponseNotFound() 366 | else : 367 | response.WriteResponseMethodNotAllowed() 368 | elif upg == 'websocket' and 'MicroWebSocket' in globals() \ 369 | and self._microWebSrv.AcceptWebSocketCallback : 370 | MicroWebSocket( socket = self._socket, 371 | httpClient = self, 372 | httpResponse = response, 373 | maxRecvLen = self._microWebSrv.MaxWebSocketRecvLen, 374 | threaded = self._microWebSrv.WebSocketThreaded, 375 | acceptCallback = self._microWebSrv.AcceptWebSocketCallback ) 376 | return 377 | else : 378 | response.WriteResponseNotImplemented() 379 | else : 380 | response.WriteResponseBadRequest() 381 | except : 382 | response.WriteResponseInternalServerError() 383 | try : 384 | if self._socketfile is not self._socket: 385 | self._socketfile.close() 386 | self._socket.close() 387 | except : 388 | pass 389 | 390 | # ------------------------------------------------------------------------ 391 | 392 | def _parseFirstLine(self, response) : 393 | try : 394 | elements = self._socketfile.readline().decode().strip().split() 395 | if len(elements) == 3 : 396 | self._method = elements[0].upper() 397 | self._path = elements[1] 398 | self._httpVer = elements[2].upper() 399 | elements = self._path.split('?', 1) 400 | if len(elements) > 0 : 401 | self._resPath = MicroWebSrv._unquote_plus(elements[0]) 402 | if len(elements) > 1 : 403 | self._queryString = elements[1] 404 | elements = self._queryString.split('&') 405 | for s in elements : 406 | param = s.split('=', 1) 407 | if len(param) > 0 : 408 | value = MicroWebSrv._unquote(param[1]) if len(param) > 1 else '' 409 | self._queryParams[MicroWebSrv._unquote(param[0])] = value 410 | return True 411 | except : 412 | pass 413 | return False 414 | 415 | # ------------------------------------------------------------------------ 416 | 417 | def _parseHeader(self, response) : 418 | while True : 419 | elements = self._socketfile.readline().decode().strip().split(':', 1) 420 | if len(elements) == 2 : 421 | self._headers[elements[0].strip()] = elements[1].strip() 422 | elif len(elements) == 1 and len(elements[0]) == 0 : 423 | if self._method == 'POST' : 424 | self._contentType = self._headers.get("Content-Type", None) 425 | self._contentLength = int(self._headers.get("Content-Length", 0)) 426 | return True 427 | else : 428 | return False 429 | 430 | # ------------------------------------------------------------------------ 431 | 432 | def _getConnUpgrade(self) : 433 | if 'upgrade' in self._headers.get('Connection', '').lower() : 434 | return self._headers.get('Upgrade', '').lower() 435 | return None 436 | 437 | # ------------------------------------------------------------------------ 438 | 439 | def GetServer(self) : 440 | return self._microWebSrv 441 | 442 | # ------------------------------------------------------------------------ 443 | 444 | def GetAddr(self) : 445 | return self._addr 446 | 447 | # ------------------------------------------------------------------------ 448 | 449 | def GetIPAddr(self) : 450 | return self._addr[0] 451 | 452 | # ------------------------------------------------------------------------ 453 | 454 | def GetPort(self) : 455 | return self._addr[1] 456 | 457 | # ------------------------------------------------------------------------ 458 | 459 | def GetRequestMethod(self) : 460 | return self._method 461 | 462 | # ------------------------------------------------------------------------ 463 | 464 | def GetRequestTotalPath(self) : 465 | return self._path 466 | 467 | # ------------------------------------------------------------------------ 468 | 469 | def GetRequestPath(self) : 470 | return self._resPath 471 | 472 | # ------------------------------------------------------------------------ 473 | 474 | def GetRequestQueryString(self) : 475 | return self._queryString 476 | 477 | # ------------------------------------------------------------------------ 478 | 479 | def GetRequestQueryParams(self) : 480 | return self._queryParams 481 | 482 | # ------------------------------------------------------------------------ 483 | 484 | def GetRequestHeaders(self) : 485 | return self._headers 486 | 487 | # ------------------------------------------------------------------------ 488 | 489 | def GetRequestContentType(self) : 490 | return self._contentType 491 | 492 | # ------------------------------------------------------------------------ 493 | 494 | def GetRequestContentLength(self) : 495 | return self._contentLength 496 | 497 | # ------------------------------------------------------------------------ 498 | 499 | def ReadRequestContent(self, size=None) : 500 | self._socket.setblocking(False) 501 | b = None 502 | try : 503 | if not size : 504 | b = self._socketfile.read(self._contentLength) 505 | elif size > 0 : 506 | b = self._socketfile.read(size) 507 | except : 508 | pass 509 | self._socket.setblocking(True) 510 | return b if b else b'' 511 | 512 | # ------------------------------------------------------------------------ 513 | 514 | def ReadRequestPostedFormData(self) : 515 | res = { } 516 | data = self.ReadRequestContent() 517 | if len(data) > 0 : 518 | elements = data.decode().split('&') 519 | for s in elements : 520 | param = s.split('=', 1) 521 | if len(param) > 0 : 522 | value = MicroWebSrv._unquote(param[1]) if len(param) > 1 else '' 523 | res[MicroWebSrv._unquote(param[0])] = value 524 | return res 525 | 526 | # ------------------------------------------------------------------------ 527 | 528 | def ReadRequestContentAsJSON(self) : 529 | try : 530 | return loads(self.ReadRequestContent()) 531 | except : 532 | return None 533 | 534 | # ============================================================================ 535 | # ===( Class Response )====================================================== 536 | # ============================================================================ 537 | 538 | class _response : 539 | 540 | # ------------------------------------------------------------------------ 541 | 542 | def __init__(self, client) : 543 | self._client = client 544 | 545 | # ------------------------------------------------------------------------ 546 | 547 | def _write(self, data) : 548 | if type(data) == str: 549 | data = data.encode() 550 | return self._client._socketfile.write(data) 551 | 552 | # ------------------------------------------------------------------------ 553 | 554 | def _writeFirstLine(self, code) : 555 | reason = self._responseCodes.get(code, ('Unknown reason', ))[0] 556 | self._write("HTTP/1.1 %s %s\r\n" % (code, reason)) 557 | 558 | # ------------------------------------------------------------------------ 559 | 560 | def _writeHeader(self, name, value) : 561 | self._write("%s: %s\r\n" % (name, value)) 562 | 563 | # ------------------------------------------------------------------------ 564 | 565 | def _writeContentTypeHeader(self, contentType, charset=None) : 566 | if contentType : 567 | ct = contentType \ 568 | + (("; charset=%s" % charset) if charset else "") 569 | else : 570 | ct = "application/octet-stream" 571 | self._writeHeader("Content-Type", ct) 572 | 573 | # ------------------------------------------------------------------------ 574 | 575 | def _writeServerHeader(self) : 576 | self._writeHeader("Server", "MicroWebSrv by JC`zic") 577 | 578 | # ------------------------------------------------------------------------ 579 | 580 | def _writeEndHeader(self) : 581 | self._write("\r\n") 582 | 583 | # ------------------------------------------------------------------------ 584 | 585 | def _writeBeforeContent(self, code, headers, contentType, contentCharset, contentLength) : 586 | self._writeFirstLine(code) 587 | if isinstance(headers, dict) : 588 | for header in headers : 589 | self._writeHeader(header, headers[header]) 590 | if contentLength > 0 : 591 | self._writeContentTypeHeader(contentType, contentCharset) 592 | self._writeHeader("Content-Length", contentLength) 593 | self._writeServerHeader() 594 | self._writeHeader("Connection", "close") 595 | self._writeEndHeader() 596 | 597 | # ------------------------------------------------------------------------ 598 | 599 | def WriteSwitchProto(self, upgrade, headers=None) : 600 | self._writeFirstLine(101) 601 | self._writeHeader("Connection", "Upgrade") 602 | self._writeHeader("Upgrade", upgrade) 603 | if isinstance(headers, dict) : 604 | for header in headers : 605 | self._writeHeader(header, headers[header]) 606 | self._writeServerHeader() 607 | self._writeEndHeader() 608 | if self._client._socketfile is not self._client._socket : 609 | self._client._socketfile.flush() # CPython needs flush to continue protocol 610 | 611 | # ------------------------------------------------------------------------ 612 | 613 | def WriteResponse(self, code, headers, contentType, contentCharset, content) : 614 | try : 615 | contentLength = len(content) if content else 0 616 | self._writeBeforeContent(code, headers, contentType, contentCharset, contentLength) 617 | if contentLength > 0 : 618 | self._write(content) 619 | return True 620 | except : 621 | return False 622 | 623 | # ------------------------------------------------------------------------ 624 | 625 | def WriteResponsePyHTMLFile(self, filepath, headers=None) : 626 | if 'MicroWebTemplate' in globals() : 627 | with open(filepath, 'r') as file : 628 | code = file.read() 629 | mWebTmpl = MicroWebTemplate(code, escapeStrFunc=MicroWebSrv.HTMLEscape, filepath=filepath) 630 | try : 631 | tmplResult = mWebTmpl.Execute() 632 | return self.WriteResponse(200, headers, "text/html", "UTF-8", tmplResult) 633 | except Exception as ex : 634 | return self.WriteResponse( 500, 635 | None, 636 | "text/html", 637 | "UTF-8", 638 | self._execErrCtnTmpl % { 639 | 'module' : 'PyHTML', 640 | 'message' : str(ex) 641 | } ) 642 | return self.WriteResponseNotImplemented() 643 | 644 | # ------------------------------------------------------------------------ 645 | 646 | def WriteResponseFile(self, filepath, contentType=None, headers=None) : 647 | try : 648 | size = stat(filepath)[6] 649 | if size > 0 : 650 | with open(filepath, 'rb') as file : 651 | self._writeBeforeContent(200, headers, contentType, None, size) 652 | buf = MicroWebSrv._tryAllocByteArray(1024) 653 | if buf : 654 | while size > 0 : 655 | x = file.readinto(buf) 656 | if x < len(buf) : 657 | buf = memoryview(buf)[:x] 658 | self._write(buf) 659 | size -= x 660 | return True 661 | self.WriteResponseInternalServerError() 662 | return False 663 | except : 664 | pass 665 | self.WriteResponseNotFound() 666 | return False 667 | 668 | # ------------------------------------------------------------------------ 669 | 670 | def WriteResponseFileAttachment(self, filepath, attachmentName, headers=None) : 671 | if not isinstance(headers, dict) : 672 | headers = { } 673 | headers["Content-Disposition"] = "attachment; filename=\"%s\"" % attachmentName 674 | return self.WriteResponseFile(filepath, None, headers) 675 | 676 | # ------------------------------------------------------------------------ 677 | 678 | def WriteResponseOk(self, headers=None, contentType=None, contentCharset=None, content=None) : 679 | return self.WriteResponse(200, headers, contentType, contentCharset, content) 680 | 681 | # ------------------------------------------------------------------------ 682 | 683 | def WriteResponseJSONOk(self, obj=None, headers=None) : 684 | return self.WriteResponse(200, headers, "application/json", "UTF-8", dumps(obj)) 685 | 686 | # ------------------------------------------------------------------------ 687 | 688 | def WriteResponseRedirect(self, location) : 689 | headers = { "Location" : location } 690 | return self.WriteResponse(302, headers, None, None, None) 691 | 692 | # ------------------------------------------------------------------------ 693 | 694 | def WriteResponseError(self, code) : 695 | responseCode = self._responseCodes.get(code, ('Unknown reason', '')) 696 | return self.WriteResponse( code, 697 | None, 698 | "text/html", 699 | "UTF-8", 700 | self._errCtnTmpl % { 701 | 'code' : code, 702 | 'reason' : responseCode[0], 703 | 'message' : responseCode[1] 704 | } ) 705 | 706 | # ------------------------------------------------------------------------ 707 | 708 | def WriteResponseJSONError(self, code, obj=None) : 709 | return self.WriteResponse( code, 710 | None, 711 | "application/json", 712 | "UTF-8", 713 | dumps(obj if obj else { }) ) 714 | 715 | # ------------------------------------------------------------------------ 716 | 717 | def WriteResponseBadRequest(self) : 718 | return self.WriteResponseError(400) 719 | 720 | # ------------------------------------------------------------------------ 721 | 722 | def WriteResponseForbidden(self) : 723 | return self.WriteResponseError(403) 724 | 725 | # ------------------------------------------------------------------------ 726 | 727 | def WriteResponseNotFound(self) : 728 | if self._client._microWebSrv._notFoundUrl : 729 | self.WriteResponseRedirect(self._client._microWebSrv._notFoundUrl) 730 | else : 731 | return self.WriteResponseError(404) 732 | 733 | # ------------------------------------------------------------------------ 734 | 735 | def WriteResponseMethodNotAllowed(self) : 736 | return self.WriteResponseError(405) 737 | 738 | # ------------------------------------------------------------------------ 739 | 740 | def WriteResponseInternalServerError(self) : 741 | return self.WriteResponseError(500) 742 | 743 | # ------------------------------------------------------------------------ 744 | 745 | def WriteResponseNotImplemented(self) : 746 | return self.WriteResponseError(501) 747 | 748 | # ------------------------------------------------------------------------ 749 | 750 | _errCtnTmpl = """\ 751 | 752 | 753 | Error 754 | 755 | 756 |

%(code)d %(reason)s

757 | %(message)s 758 | 759 | 760 | """ 761 | 762 | # ------------------------------------------------------------------------ 763 | 764 | _execErrCtnTmpl = """\ 765 | 766 | 767 | Page execution error 768 | 769 | 770 |

%(module)s page execution error

771 | %(message)s 772 | 773 | 774 | """ 775 | 776 | # ------------------------------------------------------------------------ 777 | 778 | _responseCodes = { 779 | 100: ('Continue', 'Request received, please continue'), 780 | 101: ('Switching Protocols', 781 | 'Switching to new protocol; obey Upgrade header'), 782 | 783 | 200: ('OK', 'Request fulfilled, document follows'), 784 | 201: ('Created', 'Document created, URL follows'), 785 | 202: ('Accepted', 786 | 'Request accepted, processing continues off-line'), 787 | 203: ('Non-Authoritative Information', 'Request fulfilled from cache'), 788 | 204: ('No Content', 'Request fulfilled, nothing follows'), 789 | 205: ('Reset Content', 'Clear input form for further input.'), 790 | 206: ('Partial Content', 'Partial content follows.'), 791 | 792 | 300: ('Multiple Choices', 793 | 'Object has several resources -- see URI list'), 794 | 301: ('Moved Permanently', 'Object moved permanently -- see URI list'), 795 | 302: ('Found', 'Object moved temporarily -- see URI list'), 796 | 303: ('See Other', 'Object moved -- see Method and URL list'), 797 | 304: ('Not Modified', 798 | 'Document has not changed since given time'), 799 | 305: ('Use Proxy', 800 | 'You must use proxy specified in Location to access this ' 801 | 'resource.'), 802 | 307: ('Temporary Redirect', 803 | 'Object moved temporarily -- see URI list'), 804 | 805 | 400: ('Bad Request', 806 | 'Bad request syntax or unsupported method'), 807 | 401: ('Unauthorized', 808 | 'No permission -- see authorization schemes'), 809 | 402: ('Payment Required', 810 | 'No payment -- see charging schemes'), 811 | 403: ('Forbidden', 812 | 'Request forbidden -- authorization will not help'), 813 | 404: ('Not Found', 'Nothing matches the given URI'), 814 | 405: ('Method Not Allowed', 815 | 'Specified method is invalid for this resource.'), 816 | 406: ('Not Acceptable', 'URI not available in preferred format.'), 817 | 407: ('Proxy Authentication Required', 'You must authenticate with ' 818 | 'this proxy before proceeding.'), 819 | 408: ('Request Timeout', 'Request timed out; try again later.'), 820 | 409: ('Conflict', 'Request conflict.'), 821 | 410: ('Gone', 822 | 'URI no longer exists and has been permanently removed.'), 823 | 411: ('Length Required', 'Client must specify Content-Length.'), 824 | 412: ('Precondition Failed', 'Precondition in headers is false.'), 825 | 413: ('Request Entity Too Large', 'Entity is too large.'), 826 | 414: ('Request-URI Too Long', 'URI is too long.'), 827 | 415: ('Unsupported Media Type', 'Entity body in unsupported format.'), 828 | 416: ('Requested Range Not Satisfiable', 829 | 'Cannot satisfy request range.'), 830 | 417: ('Expectation Failed', 831 | 'Expect condition could not be satisfied.'), 832 | 833 | 500: ('Internal Server Error', 'Server got itself in trouble'), 834 | 501: ('Not Implemented', 835 | 'Server does not support this operation'), 836 | 502: ('Bad Gateway', 'Invalid responses from another server/proxy.'), 837 | 503: ('Service Unavailable', 838 | 'The server cannot process the request due to a high load'), 839 | 504: ('Gateway Timeout', 840 | 'The gateway server did not receive a timely response'), 841 | 505: ('HTTP Version Not Supported', 'Cannot fulfill request.'), 842 | } 843 | 844 | # ============================================================================ 845 | # ============================================================================ 846 | # ============================================================================ 847 | 848 | -------------------------------------------------------------------------------- /media/interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vsolina/micropython-web-editor/cd33e4bee9fb3325d91705aa150fb2da47b11724/media/interface.png -------------------------------------------------------------------------------- /weditor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MicroPython Editor 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 |
16 | LOADING FILES 17 |
18 |
19 |
LOADING EDITOR
20 |
21 |
22 |
23 | 24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /weditor/js/script.js: -------------------------------------------------------------------------------- 1 | function initEditor() { 2 | var editor = ace.edit("editor"); 3 | window.globalEditor = editor; 4 | editor.setTheme("ace/theme/dracula"); 5 | editor.session.setMode("ace/mode/plain_text"); 6 | editor.setValue("\n<- Select a file to edit\n"); 7 | window.editor = editor; 8 | loadDirPath("/"); 9 | } 10 | 11 | function showNotification(text, seconds) { 12 | var notification = document.getElementById("notification"); 13 | notification.innerHTML = text; 14 | notification.style.display = "block"; 15 | if (seconds) { 16 | setTimeout(hideNotification, seconds * 1000); 17 | } 18 | } 19 | function hideNotification() { 20 | var notification = document.getElementById("notification"); 21 | notification.style.display = "none"; 22 | } 23 | 24 | function actionRefreshDirectory() { 25 | loadDirPath(window.activeFileDir); 26 | } 27 | function actionActivateDirectory(event) { 28 | var dpath = event.target.innerHTML; 29 | window.activeFileDir += dpath + "/"; 30 | loadDirPath(window.activeFileDir); 31 | } 32 | function actionActivateParentDirectory(event) { 33 | var components = window.activeFileDir.split("/"); 34 | if (components.length <= 2) return; 35 | components.splice(components.length - 2, 1); 36 | window.activeFileDir = components.join("/"); 37 | loadDirPath(window.activeFileDir); 38 | } 39 | 40 | function actionCreateNewFile(event) { 41 | function createdFile(response) { 42 | loadDirPath(window.activeFileDir); 43 | } 44 | var fname = prompt("New file name"); 45 | if (fname) { 46 | fpath = window.activeFileDir + fname; 47 | showNotification("Creating file: " + fpath); 48 | fetch("/newfile?path=" + fpath).then(response => response.json()).then(json => createdFile(json)); 49 | } 50 | } 51 | function actionActivateFile(event) { 52 | var fpath = window.activeFileDir + event.target.innerHTML; 53 | console.log("Loading file ", "==" + fpath + "=="); 54 | showNotification("Loading file: " + fpath); 55 | fetch("/file?path=" + fpath).then(response => response.json()).then(json => loadedFile(fpath, json['lines'])); 56 | } 57 | function loadedFile(fname, lines) { 58 | editor.setValue(lines); 59 | var components = fname.split("."); 60 | var mode = "plain_text"; 61 | if (components.length > 1) { 62 | var ext = components[components.length - 1]; 63 | if (ext == "py") mode = "python"; 64 | if (ext == "html") mode = "html"; 65 | if (ext == "css") mode = "css"; 66 | if (ext == "js") mode = "javascript"; 67 | } 68 | editor.session.setMode("ace/mode/" + mode); 69 | window.activeFilePath = fname; 70 | window.activeFileName = fname; 71 | editor.selection.clearSelection(); 72 | showNotification("Loaded: " + fname, 1); 73 | } 74 | function actionSaveFile() { 75 | showNotification("Saving file: " + window.activeFilePath); 76 | fetch('/savefile', { 77 | method: 'POST', 78 | headers: { 79 | 'Content-Type': 'application/json', 80 | }, 81 | body: JSON.stringify({"lines": globalEditor.getValue(), "path": window.activeFilePath}), 82 | }) 83 | .then((response) => response.json()) 84 | .then((data) => { 85 | console.log('Success saving:', data); 86 | showNotification("Saved", 1); 87 | }) 88 | .catch((error) => { 89 | console.error('Error saving:', error); 90 | showNotification("Error saving file: " + error, 4); 91 | }); 92 | } 93 | 94 | function loadDirPath(path) { 95 | window.activeFileDir = path; 96 | showNotification("Loading directory: " + path); 97 | fetch("/dir?path=" + path).then(response => response.json()).then(json => updateFileList(json)); 98 | } 99 | function updateFileList(json) { 100 | var files = json.files; 101 | var dirs = json.dirs; 102 | var left = document.getElementById("left"); 103 | left.innerHTML = ""; 104 | 105 | function addSpan(title, className, action) { 106 | var span = document.createElement("span"); 107 | span.innerHTML = title; 108 | span.className = className; 109 | if (action) { span.onclick = action; } 110 | left.appendChild(span); 111 | } 112 | 113 | addSpan("↻ refresh", "left_element left_action", actionRefreshDirectory); 114 | addSpan("DIRS", "left_title"); 115 | 116 | if (window.activeFileDir != "/") { 117 | addSpan("..", "left_element left_dir", actionActivateParentDirectory); 118 | } 119 | for (var dirName of dirs) { 120 | addSpan(dirName, "left_element left_dir", actionActivateDirectory); 121 | } 122 | 123 | addSpan("FILES", "left_title"); 124 | addSpan("new file...", "left_element left_action", actionCreateNewFile); 125 | 126 | for (var fileName of files) { 127 | addSpan(fileName, "left_element left_file", actionActivateFile); 128 | } 129 | showNotification("Loaded directory: " + window.activeFileDir, 1); 130 | } 131 | 132 | function handleKeyDown(event) { 133 | if ((event.ctrlKey || event.metaKey) && event.code == 'KeyS') { 134 | actionSaveFile(); 135 | event.preventDefault(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /weditor/js/term.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * term.js - an xterm emulator 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | * https://github.com/chjj/term.js 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | * Originally forked from (with the author's permission): 25 | * Fabrice Bellard's javascript vt100 for jslinux: 26 | * http://bellard.org/jslinux/ 27 | * Copyright (c) 2011 Fabrice Bellard 28 | * The original design remains. The terminal itself 29 | * has been extended to include xterm CSI codes, among 30 | * other features. 31 | */ 32 | 33 | !function(){"use strict";var window=this,document=this.document;function EventEmitter(){this._events=this._events||{}}function Stream(){EventEmitter.call(this)}EventEmitter.prototype.on=EventEmitter.prototype.addListener=function(type,listener){this._events[type]=this._events[type]||[],this._events[type].push(listener)},EventEmitter.prototype.off=EventEmitter.prototype.removeListener=function(type,listener){if(this._events[type])for(var obj=this._events[type],i=obj.length;i--;)if(obj[i]===listener||obj[i].listener===listener)return void obj.splice(i,1)},EventEmitter.prototype.removeAllListeners=function(type){this._events[type]&&delete this._events[type]},EventEmitter.prototype.once=function(type,listener){function on(){var args=Array.prototype.slice.call(arguments);return this.removeListener(type,on),listener.apply(this,args)}return on.listener=listener,this.on(type,on)},EventEmitter.prototype.emit=function(type){if(this._events[type])for(var args=Array.prototype.slice.call(arguments,1),obj=this._events[type],l=obj.length,i=0;i>16&255,color>>8&255,255&color]);return out}(),Terminal.defaults={colors:Terminal.colors,convertEol:!1,termName:"xterm",geometry:[80,24],cursorBlink:!0,visualBell:!1,popOnBell:!1,scrollback:1e3,screenKeys:!1,debug:!1,useStyle:!1},Terminal.options={},each(keys(Terminal.defaults),function(key){Terminal[key]=Terminal.defaults[key],Terminal.options[key]=Terminal.defaults[key]}),Terminal.focus=null,Terminal.prototype.focus=function(){Terminal.focus!==this&&(Terminal.focus&&Terminal.focus.blur(),this.sendFocus&&this.send(""),this.showCursor(),Terminal.focus=this)},Terminal.prototype.blur=function(){Terminal.focus===this&&(this.cursorState=0,this.refresh(this.y,this.y),this.sendFocus&&this.send(""),Terminal.focus=null)},Terminal.prototype.initGlobal=function(){var document=this.document;Terminal._boundDocs=Terminal._boundDocs||[],~function(obj,el){var i=obj.length;for(;i--;)if(obj[i]===el)return i;return-1}(Terminal._boundDocs,document)||(Terminal._boundDocs.push(document),Terminal.bindPaste(document),Terminal.bindKeys(document),Terminal.bindCopy(document),this.isMobile&&this.fixMobile(document),this.useStyle&&Terminal.insertStyle(document,this.colors[256],this.colors[257]))},Terminal.bindPaste=function(document){on(document.defaultView,"paste",function(ev){var term=Terminal.focus;if(term)return ev.clipboardData?term.send(ev.clipboardData.getData("text/plain")):term.context.clipboardData&&term.send(term.context.clipboardData.getData("Text")),term.element.contentEditable="inherit",cancel(ev)})},Terminal.bindKeys=function(document){on(document,"keydown",function(ev){var target;return Terminal.focus&&(target=ev.target||ev.srcElement)&&(target===Terminal.focus.element||target===Terminal.focus.context||target===Terminal.focus.document||target===Terminal.focus.body||target===Terminal._textarea||target===Terminal.focus.parent)?Terminal.focus.keyDown(ev):void 0},!0),on(document,"keypress",function(ev){var target;return Terminal.focus&&(target=ev.target||ev.srcElement)&&!(ev.ctrlKey&&"v"===ev.key||target!==Terminal.focus.element&&target!==Terminal.focus.context&&target!==Terminal.focus.document&&target!==Terminal.focus.body&&target!==Terminal._textarea&&target!==Terminal.focus.parent)?(Terminal.focus.element.contentEditable="inherit",Terminal.focus.keyPress(ev)):void 0},!0),on(document,"mousedown",function(ev){if(Terminal.focus){var el=ev.target||ev.srcElement;if(el){do{if(el===Terminal.focus.element)return}while(el=el.parentNode);Terminal.focus.blur()}}})},Terminal.bindCopy=function(document){on(document.defaultView,"copy",function(ev){var textarea,text,term=Terminal.focus;term&&term._selected&&(textarea=term.getCopyTextarea(),text=term.grabText(term._selected.x1,term._selected.x2,term._selected.y1,term._selected.y2),term.emit("copy",text),textarea.focus(),textarea.textContent=text,textarea.value=text,textarea.setSelectionRange(0,text.length),setTimeout(function(){term.element.focus(),term.focus()},1))})},Terminal.prototype.fixMobile=function(document){var self=this,textarea=document.createElement("textarea");textarea.style.position="absolute",textarea.style.left="-32000px",textarea.style.top="-32000px",textarea.style.width="0px",textarea.style.height="0px",textarea.style.opacity="0",textarea.style.backgroundColor="transparent",textarea.style.borderStyle="none",textarea.style.outlineStyle="none",textarea.autocapitalize="none",textarea.autocorrect="off",document.getElementsByTagName("body")[0].appendChild(textarea),Terminal._textarea=textarea,setTimeout(function(){textarea.focus()},1e3),this.isAndroid&&on(textarea,"change",function(){var value=textarea.textContent||textarea.value;textarea.value="",textarea.textContent="",self.send(value+"\r")})},Terminal.insertStyle=function(document,bg,fg){var head,style=document.getElementById("term-style");style||(head=document.getElementsByTagName("head")[0])&&((style=document.createElement("style")).id="term-style",style.innerHTML=".terminal {\n float: left;\n border: "+bg+' solid 5px;\n font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;\n font-size: 11px;\n color: '+fg+";\n background: "+bg+";\n}\n\n.terminal-cursor {\n color: "+bg+";\n background: "+fg+";\n}\n",head.insertBefore(style,head.firstChild))},Terminal.prototype.open=function(parent){var div,body,terminal,line,self=this,i=0;if(this.parent=parent||this.parent,!this.parent)throw new Error("Terminal requires a parent element.");for(this.context=this.parent.ownerDocument.defaultView,this.document=this.parent.ownerDocument,this.body=this.document.getElementsByTagName("body")[0],this.context.navigator&&this.context.navigator.userAgent&&(this.isMac=!!~this.context.navigator.userAgent.indexOf("Mac"),this.isIpad=!!~this.context.navigator.userAgent.indexOf("iPad"),this.isIphone=!!~this.context.navigator.userAgent.indexOf("iPhone"),this.isAndroid=!!~this.context.navigator.userAgent.indexOf("Android"),this.isMobile=this.isIpad||this.isIphone||this.isAndroid,this.isMSIE=!!~this.context.navigator.userAgent.indexOf("MSIE"),this.isFirefox=!!~this.context.navigator.userAgent.indexOf("Firefox")),this.element=this.document.createElement("div"),this.element.className="terminal",this.element.style.outline="none",this.element.setAttribute("tabindex",0),this.element.setAttribute("spellcheck","false"),this.element.style.backgroundColor=this.colors[256],this.element.style.color=this.colors[257],this.children=[];i>6),data.push(128|63&ch))}else{if(255===ch)return data.push(0);data.push(ch=127self.cols&&(x=self.cols),(y=y<0?0:y)>self.rows&&(y=self.rows),{x:x+=32,y:y+=32,type:ev.type===wheelEvent?"mousewheel":ev.type}}}on(el,"mousedown",function(ev){if(self.mouseEvents)return sendButton(ev),self.focus(),self.normalMouse&&on(self.document,"mousemove",sendMove),self.x10Mouse||on(self.document,"mouseup",function up(ev){return sendButton(ev),self.normalMouse&&off(self.document,"mousemove",sendMove),off(self.document,"mouseup",up),cancel(ev)}),cancel(ev)}),on(el,wheelEvent,function(ev){if(self.mouseEvents&&!(self.x10Mouse||self.vt300Mouse||self.decLocator))return sendButton(ev),cancel(ev)}),on(el,wheelEvent,function(ev){if(!self.mouseEvents&&!self.applicationKeypad)return"DOMMouseScroll"===ev.type?self.scrollDisp(ev.detail<0?-5:5):self.scrollDisp(0=this.rows/2&&(parent=this.element.parentNode)&&parent.removeChild(this.element),width=this.cols,y=start,end>=this.lines.length&&(this.log("`end` is too large. Most likely a bad CSR."),end=this.lines.length-1);y<=end;y++){for(row=y+this.ydisp,line=this.lines[row],out="",x=y!==this.y||!this.cursorState||this.ydisp!==this.ybase&&!this.selectMode||this.cursorHidden?-1:this.x,attr=this.defAttr,i=0;i"),data!==this.defAttr&&(-1===data?out+='':(out+='>9&511,fg=511&data,1&flags&&fg<8&&(fg+=8)),16&flags&&(out+="visibility:hidden;"),256!==bg&&(out+="background-color:"+this.colors[bg]+";"),257!==fg&&(out+="color:"+this.colors[fg]+";"),out+='">'))),ch){case"&":out+="&";break;case"<":out+="<";break;case">":out+=">";break;default:ch<=" "?out+=" ":(isWide(ch)&&i++,out+=ch)}attr=data}attr!==this.defAttr&&(out+=""),this.children[y].innerHTML=out}parent&&parent.appendChild(this.element)},Terminal.prototype._cursorBlink=function(){Terminal.focus===this&&(this.cursorState^=1,this.refresh(this.y,this.y))},Terminal.prototype.showCursor=function(){this.cursorState||(this.cursorState=1,this.refresh(this.y,this.y))},Terminal.prototype.startBlink=function(){var self;this.cursorBlink&&((self=this)._blinker=function(){self._cursorBlink()},this._blink=setInterval(this._blinker,500))},Terminal.prototype.refreshBlink=function(){this.cursorBlink&&this._blink&&(clearInterval(this._blink),this._blink=setInterval(this._blinker,500))},Terminal.prototype.scroll=function(){var row;++this.ybase===this.scrollback&&(this.ybase=this.ybase/2|0,this.lines=this.lines.slice(1-(this.ybase+this.rows))),this.ydisp=this.ybase,row=this.ybase+this.rows-1,(row-=this.rows-1-this.scrollBottom)===this.lines.length?this.lines.push(this.blankLine()):this.lines.splice(row,0,this.blankLine()),0!==this.scrollTop&&(0!==this.ybase&&(this.ybase--,this.ydisp=this.ybase),this.lines.splice(this.ybase+this.scrollTop,1)),this.updateRange(this.scrollTop),this.updateRange(this.scrollBottom)},Terminal.prototype.scrollDisp=function(disp){this.ydisp+=disp,this.ydisp>this.ybase?this.ydisp=this.ybase:this.ydisp<0&&(this.ydisp=0),this.refresh(0,this.rows-1)},Terminal.prototype.write=function(data){var j,cs,ch,l=data.length,i=0;for(this.refreshStart=this.y,this.refreshEnd=this.y,this.ybase!==this.ydisp&&(this.ydisp=this.ybase,this.maxRange());ithis.scrollBottom&&(this.y--,this.scroll());break;case"\r":this.x=0;break;case"\b":0=this.cols&&(this.x=0,this.y++,this.y>this.scrollBottom&&(this.y--,this.scroll())),this.lines[this.y+this.ybase][this.x]=[this.curAttr,ch],this.x++,this.updateRange(this.y),isWide(ch))){if(j=this.y+this.ybase,this.cols<2||this.x>=this.cols){this.lines[j][this.x-1]=[this.curAttr," "];break}this.lines[j][this.x]=[this.curAttr," "],this.x++}}break;case 1:switch(ch){case"[":this.params=[],this.currentParam=0,this.state=2;break;case"]":this.params=[],this.currentParam=0,this.state=3;break;case"P":this.params=[],this.prefix="",this.currentParam="",this.state=5;break;case"_":case"^":this.state=6;break;case"c":this.reset();break;case"E":this.x=0;case"D":this.index();break;case"M":this.reverseIndex();break;case"%":this.setgLevel(0),this.setgCharset(0,Terminal.charsets.US),this.state=0,i++;break;case"(":case")":case"*":case"+":case"-":case".":switch(ch){case"(":this.gcharset=0;break;case")":this.gcharset=1;break;case"*":this.gcharset=2;break;case"+":this.gcharset=3;break;case"-":this.gcharset=1;break;case".":this.gcharset=2}this.state=4;break;case"/":this.gcharset=3,this.state=4,i--;break;case"N":case"O":break;case"n":this.setgLevel(2);break;case"o":case"|":this.setgLevel(3);break;case"}":this.setgLevel(2);break;case"~":this.setgLevel(1);break;case"7":this.saveCursor(),this.state=0;break;case"8":this.restoreCursor(),this.state=0;break;case"#":this.state=0,i++;break;case"H":this.tabSet();break;case"=":this.log("Serial port requested application keypad."),this.applicationKeypad=!0,this.state=0;break;case">":this.log("Switching back to normal keypad."),this.applicationKeypad=!1,this.state=0;break;default:this.state=0,this.error("Unknown ESC control: %s.",ch)}break;case 4:switch(ch){case"0":cs=Terminal.charsets.SCLD;break;case"A":cs=Terminal.charsets.UK;break;case"B":cs=Terminal.charsets.US;break;case"4":cs=Terminal.charsets.Dutch;break;case"C":case"5":cs=Terminal.charsets.Finnish;break;case"R":cs=Terminal.charsets.French;break;case"Q":cs=Terminal.charsets.FrenchCanadian;break;case"K":cs=Terminal.charsets.German;break;case"Y":cs=Terminal.charsets.Italian;break;case"E":case"6":cs=Terminal.charsets.NorwegianDanish;break;case"Z":cs=Terminal.charsets.Spanish;break;case"H":case"7":cs=Terminal.charsets.Swedish;break;case"=":cs=Terminal.charsets.Swiss;break;case"/":cs=Terminal.charsets.ISOLatin,i++;break;default:cs=Terminal.charsets.US}this.setgCharset(this.gcharset,cs),this.gcharset=null,this.state=0;break;case 3:if(""===this.lch&&"\\"===ch||""===ch){switch(""===this.lch&&("string"==typeof this.currentParam?this.currentParam=this.currentParam.slice(0,-1):"number"==typeof this.currentParam&&(this.currentParam=(this.currentParam-("".charCodeAt(0)-48))/10)),this.params.push(this.currentParam),this.params[0]){case 0:case 1:case 2:this.params[1]&&(this.title=this.params[1],this.handleTitle(this.title))}this.params=[],this.currentParam=0,this.state=0}else this.params.length?this.currentParam+=ch:"0"<=ch&&ch<="9"?this.currentParam=10*this.currentParam+ch.charCodeAt(0)-48:";"===ch&&(this.params.push(this.currentParam),this.currentParam="");break;case 2:if("?"===ch||">"===ch||"!"===ch)this.prefix=ch;else if("0"<=ch&&ch<="9")this.currentParam=10*this.currentParam+ch.charCodeAt(0)-48;else if("$"===ch||'"'===ch||" "===ch||"'"===ch)this.postfix=ch;else if(this.params.push(this.currentParam),this.currentParam=0,";"!==ch){switch(this.state=0,ch){case"A":this.cursorUp(this.params);break;case"B":this.cursorDown(this.params);break;case"C":this.cursorForward(this.params);break;case"D":this.cursorBackward(this.params);break;case"H":this.cursorPos(this.params);break;case"J":this.eraseInDisplay(this.params);break;case"K":this.eraseInLine(this.params);break;case"m":this.prefix||this.charAttributes(this.params);break;case"n":this.prefix||this.deviceStatus(this.params);break;case"@":this.insertChars(this.params);break;case"E":this.cursorNextLine(this.params);break;case"F":this.cursorPrecedingLine(this.params);break;case"G":this.cursorCharAbsolute(this.params);break;case"L":this.insertLines(this.params);break;case"M":this.deleteLines(this.params);break;case"P":this.deleteChars(this.params);break;case"X":this.eraseChars(this.params);break;case"`":this.charPosAbsolute(this.params);break;case"a":this.HPositionRelative(this.params);break;case"c":this.sendDeviceAttributes(this.params);break;case"d":this.linePosAbsolute(this.params);break;case"e":this.VPositionRelative(this.params);break;case"f":this.HVPosition(this.params);break;case"h":this.setMode(this.params);break;case"l":this.resetMode(this.params);break;case"r":this.setScrollRegion(this.params);break;case"s":this.saveCursor(this.params);break;case"u":this.restoreCursor(this.params);break;case"I":this.cursorForwardTab(this.params);break;case"S":this.scrollUp(this.params);break;case"T":this.params.length<2&&!this.prefix&&this.scrollDown(this.params);break;case"Z":this.cursorBackwardTab(this.params);break;case"b":this.repeatPrecedingCharacter(this.params);break;case"g":this.tabClear(this.params);break;case"p":"!"===this.prefix&&this.softReset(this.params);break;default:this.error("Unknown CSI code: %s.",ch)}this.prefix="",this.postfix=""}break;case 5:if(""===this.lch&&"\\"===ch||""===ch){if("tmux;"===this.prefix&&""===ch){this.currentParam+=ch;continue}""===this.lch&&("string"==typeof this.currentParam?this.currentParam=this.currentParam.slice(0,-1):"number"==typeof this.currentParam&&(this.currentParam=(this.currentParam-("".charCodeAt(0)-48))/10)),this.params.push(this.currentParam);var pt=this.params[this.params.length-1];switch(this.prefix){case UDK:this.emit("udk",{clearAll:0===this.params[0],eraseBelow:1===this.params[0],lockKeys:0===this.params[1],dontLockKeys:1===this.params[1],keyList:(this.params[2]+"").split(";").map(function(part){return{keyCode:(part=part.split("/"))[0],hexKeyValue:part[1]}})});break;case"$q":var valid=0;switch(pt){case'"q':pt='0"q',valid=1;break;case'"p':pt='61;0"p',valid=1;break;case"r":pt=this.scrollTop+1+";"+(this.scrollBottom+1)+"r",valid=1;break;case"m":valid=0;break;default:this.error("Unknown DCS Pt: %s.",pt),valid=0}this.send("P"+valid+"$r"+pt+"\\");break;case"+p":this.emit("set terminfo",{name:this.params[0]});break;case"+q":valid=!1;this.send("P"+ +valid+"+r"+pt+"\\");break;case"tmux;":this.emit("passthrough",pt);break;default:this.error("Unknown DCS prefix: %s.",pt)}this.currentParam=0,this.prefix="",this.state=0}else this.currentParam+=ch,this.prefix||(/^\d*;\d*\|/.test(this.currentParam)?(this.prefix=UDK,this.params=this.currentParam.split(/[;|]/).map(function(n){return n.length?+n:0}).slice(0,-1),this.currentParam=""):(/^[$+][a-zA-Z]/.test(this.currentParam)||/^\w+;\x1b/.test(this.currentParam))&&(this.prefix=this.currentParam,this.currentParam=""));break;case 6:(""===this.lch&&"\\"===ch||""===ch)&&(this.state=0)}return this.updateRange(this.y),this.refresh(this.refreshStart,this.refreshEnd),!0},Terminal.prototype.writeln=function(data){return this.write(data+"\r\n")},Terminal.prototype.end=function(data){var ret=!0;return data&&(ret=this.write(data)),this.destroySoon(),ret},Terminal.prototype.resume=function(){},Terminal.prototype.pause=function(){},Terminal.prototype.keyDown=function(ev){var key,self=this;switch(ev.keyCode){case 8:ev.altKey?key="":key=ev.shiftKey?"\b":"";break;case 9:ev.shiftKey?key="":key="\t";break;case 13:key="\r";break;case 27:key="";break;case 32:key=" ";break;case 37:this.applicationCursor?key="OD":key=ev.ctrlKey?"":"";break;case 39:this.applicationCursor?key="OC":key=ev.ctrlKey?"":"";break;case 38:if(this.applicationCursor)key="OA";else{if(ev.ctrlKey)return this.scrollDisp(-1),cancel(ev);key=""}break;case 40:if(this.applicationCursor)key="OB";else{if(ev.ctrlKey)return this.scrollDisp(1),cancel(ev);key=""}break;case 46:key="[3~";break;case 45:key="[2~";break;case 36:this.applicationKeypad?key="OH":key="OH";break;case 35:this.applicationKeypad?key="OF":key="OF";break;case 33:if(ev.shiftKey)return this.scrollDisp(-(this.rows-1)),cancel(ev);key="[5~";break;case 34:if(ev.shiftKey)return this.scrollDisp(this.rows-1),cancel(ev);key="[6~";break;case 112:key="OP";break;case 113:key="OQ";break;case 114:key="OR";break;case 115:key="OS";break;case 116:key="[15~";break;case 117:key="[17~";break;case 118:key="[18~";break;case 119:key="[19~";break;case 120:key="[20~";break;case 121:key="[21~";break;case 122:key="[23~";break;case 123:key="[24~";break;default:if(ev.ctrlKey)if(65<=ev.keyCode&&ev.keyCode<=90){if(this.screenKeys&&!this.prefixMode&&!this.selectMode&&65===ev.keyCode)return this.enterPrefix(),cancel(ev);if(this.prefixMode&&86===ev.keyCode)return this.leavePrefix(),void(Terminal.focus.element.contentEditable="true");if((this.prefixMode||this.selectMode)&&67===ev.keyCode)return void(this.visualMode&&setTimeout(function(){self.leaveVisual()},1));key=String.fromCharCode(ev.keyCode-64)}else 32===ev.keyCode?key=String.fromCharCode(0):51<=ev.keyCode&&ev.keyCode<=55?key=String.fromCharCode(ev.keyCode-51+27):56===ev.keyCode?key=String.fromCharCode(127):219===ev.keyCode?key=String.fromCharCode(27):221===ev.keyCode&&(key=String.fromCharCode(29));else ev.altKey&&(65<=ev.keyCode&&ev.keyCode<=90?key=""+String.fromCharCode(ev.keyCode+32):192===ev.keyCode?key="`":48<=ev.keyCode&&ev.keyCode<=57&&(key=""+(ev.keyCode-48)))}return!key||(this.prefixMode?this.leavePrefix():this.selectMode?this.keySelect(ev,key):(this.emit("keydown",ev),this.emit("key",key,ev),this.showCursor(),this.handler(key)),cancel(ev))},Terminal.prototype.setgLevel=function(g){this.glevel=g,this.charset=this.charsets[g]},Terminal.prototype.setgCharset=function(g,charset){this.charsets[g]=charset,this.glevel===g&&(this.charset=charset)},Terminal.prototype.keyPress=function(ev){var key;if(cancel(ev),ev.charCode)key=ev.charCode;else if(null==ev.which)key=ev.keyCode;else{if(0===ev.which||0===ev.charCode)return!1;key=ev.which}return!key||ev.ctrlKey||ev.altKey||ev.metaKey||(key=String.fromCharCode(key),this.prefixMode?(this.leavePrefix(),this.keyPrefix(ev,key)):this.selectMode?this.keySelect(ev,key):(this.emit("keypress",key,ev),this.emit("key",key,ev),this.showCursor(),this.handler(key))),!1},Terminal.prototype.send=function(data){var self=this;this.queue||setTimeout(function(){self.handler(self.queue),self.queue=""},1),this.queue+=data},Terminal.prototype.bell=function(){var self;this.emit("bell"),this.visualBell&&((self=this).element.style.borderColor="white",setTimeout(function(){self.element.style.borderColor=""},10),this.popOnBell&&this.focus())},Terminal.prototype.log=function(){var args;this.debug&&this.context.console&&this.context.console.log&&(args=Array.prototype.slice.call(arguments),this.context.console.log.apply(this.context.console,args))},Terminal.prototype.error=function(){var args;this.debug&&this.context.console&&this.context.console.error&&(args=Array.prototype.slice.call(arguments),this.context.console.error.apply(this.context.console,args))},Terminal.prototype.resize=function(x,y){var line,el,i,j,ch;if(y<1&&(y=1),(j=this.cols)<(x=x<1?1:x))for(ch=[this.defAttr," "],i=this.lines.length;i--;)for(;this.lines[i].lengthx;)this.lines[i].pop();if(this.setupStops(j),this.cols=x,this.columns=x,(j=this.rows)y;)this.lines.length>y+this.ybase&&this.lines.pop(),this.children.length>y&&(el=this.children.pop())&&el.parentNode.removeChild(el);this.rows=y,this.y>=y&&(this.y=y-1),this.x>=x&&(this.x=x-1),this.scrollTop=0,this.scrollBottom=y-1,this.refresh(0,this.rows-1),this.normal=null,this.emit("resize")},Terminal.prototype.updateRange=function(y){ythis.refreshEnd&&(this.refreshEnd=y)},Terminal.prototype.maxRange=function(){this.refreshStart=0,this.refreshEnd=this.rows-1},Terminal.prototype.setupStops=function(i){for(null!=i?this.tabs[i]||(i=this.prevStop(i)):(this.tabs={},i=0);i=this.cols?this.cols-1:x<0?0:x},Terminal.prototype.nextStop=function(x){for(null==x&&(x=this.x);!this.tabs[++x]&&x=this.cols?this.cols-1:x<0?0:x},Terminal.prototype.eraseAttr=function(){return-512&this.defAttr|511&this.curAttr},Terminal.prototype.eraseRight=function(x,y){for(var line=this.lines[this.ybase+y],ch=[this.eraseAttr()," "];xthis.scrollBottom&&(this.y--,this.scroll()),this.state=0},Terminal.prototype.reverseIndex=function(){var j;this.y--,this.y=this.rows&&(this.y=this.rows-1)},Terminal.prototype.cursorForward=function(params){params=params[0];this.x+=params=params<1?1:params,this.x>=this.cols&&(this.x=this.cols-1)},Terminal.prototype.cursorBackward=function(params){params=params[0];this.x-=params=params<1?1:params,this.x<0&&(this.x=0)},Terminal.prototype.cursorPos=function(params){var row=params[0]-1,params=2<=params.length?params[1]-1:0;row<0?row=0:row>=this.rows&&(row=this.rows-1),params<0?params=0:params>=this.cols&&(params=this.cols-1),this.x=params,this.y=row},Terminal.prototype.eraseInDisplay=function(params){var j;switch(params[0]){case 0:for(this.eraseRight(this.x,this.y),j=this.y+1;j>18,fg=this.curAttr>>9&511,bg=511&this.curAttr;i>18,fg=this.defAttr>>9&511,bg=511&this.defAttr):1===p?flags|=1:4===p?flags|=2:5===p?flags|=4:7===p?flags|=8:8===p?flags|=16:22===p?flags&=-2:24===p?flags&=-3:25===p?flags&=-5:27===p?flags&=-9:28===p?flags&=-17:39===p?fg=this.defAttr>>9&511:49===p?bg=511&this.defAttr:38===p?2===params[i+1]?(-1===(fg=matchColor(255¶ms[i+=2],255¶ms[i+1],255¶ms[i+2]))&&(fg=511),i+=2):5===params[i+1]&&(fg=p=255¶ms[i+=2]):48===p?2===params[i+1]?(-1===(bg=matchColor(255¶ms[i+=2],255¶ms[i+1],255¶ms[i+2]))&&(bg=511),i+=2):5===params[i+1]&&(bg=p=255¶ms[i+=2]):100===p?(fg=this.defAttr>>9&511,bg=511&this.defAttr):this.error("Unknown SGR attribute: %d.",p);this.curAttr=flags<<18|fg<<9|bg}},Terminal.prototype.deviceStatus=function(params){if(this.prefix)"?"===this.prefix&&6===params[0]&&this.send("[?"+(this.y+1)+";"+(this.x+1)+"R");else switch(params[0]){case 5:this.send("");break;case 6:this.send("["+(this.y+1)+";"+(this.x+1)+"R")}},Terminal.prototype.insertChars=function(params){var row,j,ch,param=params[0];for(param<1&&(param=1),row=this.y+this.ybase,j=this.x,ch=[this.eraseAttr()," "];param--&&j=this.rows&&(this.y=this.rows-1),this.x=0},Terminal.prototype.cursorPrecedingLine=function(params){params=params[0];this.y-=params=params<1?1:params,this.y<0&&(this.y=0),this.x=0},Terminal.prototype.cursorCharAbsolute=function(params){params=params[0];this.x=(params=params<1?1:params)-1},Terminal.prototype.insertLines=function(params){var row,j,param=params[0];for(param<1&&(param=1),row=this.y+this.ybase,j=this.rows-1-this.scrollBottom,j=this.rows-1+this.ybase-j+1;param--;)this.lines.splice(row,0,this.blankLine(!0)),this.lines.splice(j,1);this.updateRange(this.y),this.updateRange(this.scrollBottom)},Terminal.prototype.deleteLines=function(params){var row,j,param=params[0];for(param<1&&(param=1),row=this.y+this.ybase,j=this.rows-1-this.scrollBottom,j=this.rows-1+this.ybase-j;param--;)this.lines.splice(1+j,0,this.blankLine(!0)),this.lines.splice(row,1);this.updateRange(this.y),this.updateRange(this.scrollBottom)},Terminal.prototype.deleteChars=function(params){var row,ch,param=params[0];for(param<1&&(param=1),row=this.y+this.ybase,ch=[this.eraseAttr()," "];param--;)this.lines[row].splice(this.x,1),this.lines[row].push(ch)},Terminal.prototype.eraseChars=function(params){var row,j,ch,param=params[0];for(param<1&&(param=1),row=this.y+this.ybase,j=this.x,ch=[this.eraseAttr()," "];param--&&j=this.cols&&(this.x=this.cols-1)},Terminal.prototype.HPositionRelative=function(params){params=params[0];this.x+=params=params<1?1:params,this.x>=this.cols&&(this.x=this.cols-1)},Terminal.prototype.sendDeviceAttributes=function(params){0"===this.prefix&&(this.is("xterm")?this.send("[>0;276;0c"):this.is("rxvt-unicode")?this.send("[>85;95;0c"):this.is("linux")?this.send(params[0]+"c"):this.is("screen")&&this.send("[>83;40003;0c")):this.is("xterm")||this.is("rxvt-unicode")||this.is("screen")?this.send("[?1;2c"):this.is("linux")&&this.send("[?6c"))},Terminal.prototype.linePosAbsolute=function(params){params=params[0];this.y=(params=params<1?1:params)-1,this.y>=this.rows&&(this.y=this.rows-1)},Terminal.prototype.VPositionRelative=function(params){params=params[0];this.y+=params=params<1?1:params,this.y>=this.rows&&(this.y=this.rows-1)},Terminal.prototype.HVPosition=function(params){params[0]<1&&(params[0]=1),params[1]<1&&(params[1]=1),this.y=params[0]-1,this.y>=this.rows&&(this.y=this.rows-1),this.x=params[1]-1,this.x>=this.cols&&(this.x=this.cols-1)},Terminal.prototype.setMode=function(params){if("object"==typeof params)for(var l=params.length,i=0;i=this.rows&&(this.y=this.rows-1,this.scrollDisp(1)),this.visualMode?this.selectText(this.x,this.x,y,this.ydisp+this.y):this.refresh(this.y-1,this.y);else if("h"===key||""===key)x=this.x,this.x--,this.x<0&&(this.x=0),this.visualMode?this.selectText(x,this.x,this.ydisp+this.y,this.ydisp+this.y):this.refresh(this.y,this.y);else if("l"===key||""===key)x=this.x,this.x++,this.x>=this.cols&&(this.x=this.cols-1),this.visualMode?this.selectText(x,this.x,this.ydisp+this.y,this.ydisp+this.y):this.refresh(this.y,this.y);else if("v"===key||" "===key)this.visualMode?this.leaveVisual():this.enterVisual();else if("y"===key)this.visualMode&&(ev=this.grabText(this._selected.x1,this._selected.x2,this._selected.y1,this._selected.y2),this.copyText(ev),this.leaveVisual());else if("q"===key||""===key)this.visualMode?this.leaveVisual():this.leaveSelect();else if("w"===key||"W"===key){for(var ox=this.x,oy=this.y,oyd=this.ydisp,x=this.x,y=this.y,yb=this.ydisp,saw_space=!1;;){for(var line=this.lines[yb+y];x=this.cols?this.cols-1:x)===this.cols-1&&line[x][1]<=" "))break;if(x=0,++y>=this.rows&&(y--,++yb>this.ybase)){yb=this.ybase,x=this.x;break}}this.x=x,this.y=y,this.scrollDisp(-this.ydisp+yb),this.visualMode&&this.selectText(ox,this.x,oy+oyd,this.ydisp+this.y)}else if("b"===key||"B"===key){for(var ox=this.x,oy=this.y,oyd=this.ydisp,x=this.x,y=this.y,yb=this.ydisp;;){for(line=this.lines[yb+y],saw_space=0=this.cols&&x--;;){for(line=this.lines[yb+y];x=this.cols?this.cols-1:x)===this.cols-1&&line[x][1]<=" "))break;if(x=0,++y>=this.rows&&(y--,++yb>this.ybase)){yb=this.ybase;break}}this.x=x,this.y=y,this.scrollDisp(-this.ydisp+yb),this.visualMode&&this.selectText(ox,this.x,oy+oyd,this.ydisp+this.y)}else if("^"===key||"0"===key){var ox=this.x;if("0"===key)this.x=0;else if("^"===key){for(line=this.lines[this.ydisp+this.y],x=0;x=this.cols&&(x=this.cols-1),this.x=x}this.visualMode?this.selectText(ox,this.x,this.ydisp+this.y,this.ydisp+this.y):this.refresh(this.y,this.y)}else if("$"===key){for(ox=this.x,line=this.lines[this.ydisp+this.y],x=this.cols-1;0<=x;){if(" "=this.rows&&(y--,yb=this.rows){if(y--,!(yb=this.cols)&&line[x+i][1]===entry[i];i++)if(line[x+i][1]===entry[i]&&i===entry.length-1){found=!0;break}if(found)break;x+=i+1}if(found)break;if(x=0,up){if(--y<0){if(wrapped)break;wrapped=!0,y=this.ybase+this.rows-1}}else if(++y>this.ybase+this.rows-1){if(wrapped)break;wrapped=!0,y=0}}return found?(y-this.ybase<0?(yb=y,y=0,yb>this.ybase&&(y=yb-this.ybase,yb=this.ybase)):(yb=this.ybase,y-=this.ybase),this.x=x,this.y=y,this.scrollDisp(-this.ydisp+yb),void(this.visualMode&&this.selectText(bottom,this.x,oy+oyd,this.ydisp+this.y))):void this.refresh(0,this.rows-1)}this.refresh(0,this.rows-1)}},Terminal.charsets={},Terminal.charsets.SCLD={"`":"◆",a:"▒",b:"\t",c:"\f",d:"\r",e:"\n",f:"°",g:"±",h:"␤",i:"\v",j:"┘",k:"┐",l:"┌",m:"└",n:"┼",o:"⎺",p:"⎻",q:"─",r:"⎼",s:"⎽",t:"├",u:"┤",v:"┴",w:"┬",x:"│",y:"≤",z:"≥","{":"π","|":"≠","}":"£","~":"·"},Terminal.charsets.UK=null,Terminal.charsets.US=null,Terminal.charsets.Dutch=null,Terminal.charsets.Finnish=null,Terminal.charsets.French=null,Terminal.charsets.FrenchCanadian=null,Terminal.charsets.German=null,Terminal.charsets.Italian=null,Terminal.charsets.NorwegianDanish=null,Terminal.charsets.Spanish=null,Terminal.charsets.Swedish=null,Terminal.charsets.Swiss=null,Terminal.charsets.ISOLatin=null;var String=this.String,setTimeout=this.setTimeout,setInterval=this.setInterval;function isWide(ch){return!(ch<="＀")&&("!"<=ch&&ch<="ᄒ"||"ᅡ"<=ch&&ch<="ᅦ"||"ᅧ"<=ch&&ch<="ᅬ"||"ᅭ"<=ch&&ch<="ᅲ"||"ᅳ"<=ch&&ch<="ᅵ"||"¢"<=ch&&ch<="₩"||"│"<=ch&&ch<="○")}function matchColor(r1,g1,b1){var hash=r1<<16|g1<<8|b1;if(null!=matchColor._cache[hash])return matchColor._cache[hash];for(var g2,c,r2,ldiff=1/0,li=-1,i=0;i"),document.body.insertBefore(warningDiv,document.body.childNodes[0]),term.resize(term.cols,term.rows-7))}function button_click(){connected?ws.close():(document.getElementById("url").disabled=!0,document.getElementById("button").value="Disconnect",connected=!0,connect(document.getElementById("url").value))}function prepare_for_connect(){}function update_file_status(s){}function connect(url){url.substring(5);document.location.host,(ws=new WebSocket(url)).binaryType="arraybuffer",ws.onopen=function(){term.removeAllListeners("data"),term.on("data",function(data){data=data.replace(/\n/g,"\r"),ws.send(data)}),term.on("title",function(title){document.title=title}),term.focus(),term.element.focus(),term.write("Welcome to MicroPython!\r\n"),ws.onmessage=function(event){if(event.data instanceof ArrayBuffer){var data=new Uint8Array(event.data);switch(binary_state){case 11:if(0==decode_resp(data)){for(var offset=0;offset>8&255,rec[14]=dest_fsize>>16&255,rec[15]=dest_fsize>>24&255,rec[16]=255&dest_fname.length,rec[17]=dest_fname.length>>8&255;for(var i=0;i<64;++i)i>8&255;for(var i=0;i<64;++i)i