@@ -475,6 +475,168 @@ def set_zoom(self, size):
475475 self .set_font_size (PANE_ZOOM_SIZES [size ])
476476
477477
478+ class SnekREPLPane (MicroPythonREPLPane ):
479+ """
480+ REPL = Read, Evaluate, Print, Loop.
481+
482+ This widget represents a REPL client connected to a device running
483+ Snek.
484+
485+ The device MUST be flashed with Snek for this to work.
486+ """
487+
488+ def __init__ (self , connection , theme = "day" , parent = None ):
489+ super ().__init__ (connection , theme , parent )
490+ self .getting_text = False
491+ self .text = b""
492+ self .text_recv = None
493+
494+ def keyPressEvent (self , data ):
495+ """
496+ Called when the user types something in the REPL.
497+
498+ Correctly encodes it and sends it to the connected device.
499+ """
500+ tc = self .textCursor ()
501+ key = data .key ()
502+ mod = data .modifiers ()
503+ if not mod :
504+ mod = 0
505+ ctrl_only = mod == Qt .ControlModifier
506+ meta_only = mod == Qt .MetaModifier
507+ ctrl_shift_only = mod == Qt .ControlModifier | Qt .ShiftModifier
508+ shift_down = mod & Qt .ShiftModifier
509+ on_osx = platform .system () == "Darwin"
510+
511+ if key == Qt .Key_Return :
512+ # Move cursor to the end of document before sending carriage return
513+ tc .movePosition (QTextCursor .End , mode = QTextCursor .MoveAnchor )
514+ self .device_cursor_position = tc .position ()
515+ self .send (VT100_RETURN )
516+ elif key == Qt .Key_Backspace :
517+ if not self .delete_selection ():
518+ self .send (VT100_BACKSPACE )
519+ elif key == Qt .Key_Delete :
520+ if not self .delete_selection ():
521+ self .send (VT100_DELETE )
522+ elif key == Qt .Key_Right :
523+ if shift_down :
524+ # Text selection - pass down
525+ super ().keyPressEvent (data )
526+ elif tc .hasSelection ():
527+ self .move_cursor_to (tc .selectionEnd ())
528+ elif key == Qt .Key_Left :
529+ if shift_down :
530+ # Text selection - pass down
531+ super ().keyPressEvent (data )
532+ elif tc .hasSelection ():
533+ self .move_cursor_to (tc .selectionStart ())
534+ elif (on_osx and meta_only ) or (not on_osx and ctrl_only ):
535+ # Handle the Control key. On OSX/macOS/Darwin (python calls this
536+ # platform Darwin), this is handled by Qt.MetaModifier. Other
537+ # platforms (Linux, Windows) call this Qt.ControlModifier. Go
538+ # figure. See http://doc.qt.io/qt-5/qt.html#KeyboardModifier-enum
539+ if Qt .Key_A <= key <= Qt .Key_Z :
540+ # The microbit treats an input of \x01 as Ctrl+A, etc.
541+ self .send (bytes ([1 + key - Qt .Key_A ]))
542+ elif ctrl_shift_only or (on_osx and ctrl_only ):
543+ # Command-key on Mac, Ctrl-Shift on Win/Lin
544+ if key == Qt .Key_C :
545+ self .copy ()
546+ elif key == Qt .Key_V :
547+ self .delete_selection ()
548+ self .paste ()
549+ else :
550+ self .delete_selection ()
551+ self .send (bytes (data .text (), "utf8" ))
552+
553+ def insertFromMimeData (self , source ):
554+ """
555+ Insert mime data by sending it to the REPL
556+ """
557+ if source and source .text ():
558+ text = source .text ().replace ("\r \n " , "\n " ).replace ("\r " , "\n " )
559+ self .connection .write (bytes (text , "utf8" ))
560+
561+ def set_devicecursor_to_qtcursor (self ):
562+ # Move cursor to the end of document
563+ tc = self .textCursor ()
564+ tc .movePosition (QTextCursor .End , mode = QTextCursor .MoveAnchor )
565+ self .device_cursor_position = tc .position ()
566+
567+ def send_commands (self , commands ):
568+ """
569+ Send commands to the REPL via raw mode.
570+ """
571+ raw_on = [ # Sequence of commands to get into raw mode.
572+ b"\x0e \x03 " ,
573+ ]
574+ commands = [c .encode ("utf-8" ) for c in commands ]
575+ raw_off = [
576+ b"\x0f " ,
577+ ]
578+ command_sequence = raw_on + commands + raw_off
579+ logger .info (command_sequence )
580+ self .execute (command_sequence )
581+
582+ def execute (self , commands ):
583+ """
584+ Execute a series of commands over a period of time (scheduling
585+ remaining commands to be run in the next iteration of the event loop).
586+ """
587+ if commands :
588+ command = commands [0 ]
589+ logger .info ("Sending command {}" .format (command ))
590+ self .connection .write (command )
591+ remainder = commands [1 :]
592+ remaining_task = lambda commands = remainder : self .execute (commands )
593+ QTimer .singleShot (1000 , remaining_task )
594+
595+ def process_bytes (self , data ):
596+ """
597+ Given some incoming bytes of data, work out how to handle / display
598+ them in the REPL widget.
599+ """
600+ tc = self .textCursor ()
601+ # The text cursor must be on the last line of the document. If it isn't
602+ # then move it there.
603+ while tc .movePosition (QTextCursor .Down ):
604+ pass
605+ i = 0
606+ while i < len (data ):
607+ if data [i ] == 2 : # ctrl-b
608+ self .getting_text = True
609+ self .text = b""
610+ elif data [i ] == 3 : # ctrl-c
611+ if self .text_recv :
612+ s = self .text .decode ("utf-8" , "replace" )
613+ self .text_recv .recv_text (s )
614+ self .text = b""
615+ self .getting_text = False
616+ elif data [i ] == 17 or data [i ] == 19 : # XON/XOFF
617+ pass
618+ else :
619+ if self .getting_text :
620+ if data [i ] != 13 :
621+ self .text += bytes ([data [i ]])
622+ else :
623+ if data [i ] == 8 : # \b
624+ tc .movePosition (QTextCursor .Left )
625+ self .setTextCursor (tc )
626+ elif data [i ] == 13 : # \r
627+ pass
628+ elif data [i ] == 10 : # \n
629+ tc .movePosition (QTextCursor .End )
630+ self .setTextCursor (tc )
631+ self .insertPlainText (chr (data [i ]))
632+ else :
633+ tc .deleteChar ()
634+ self .setTextCursor (tc )
635+ self .insertPlainText (chr (data [i ]))
636+ i += 1
637+ self .ensureCursorVisible ()
638+
639+
478640class MuFileList (QListWidget ):
479641 """
480642 Contains shared methods for the two types of file listing used in Mu.
0 commit comments