def __init__(self, parent, info): BaseShell.__init__(self, parent) # Get standard info if not given. if info is None and iep.config.shellConfigs2: info = iep.config.shellConfigs2[0] if not info: info = KernelInfo(None) # Store info so we can reuse it on a restart self._info = info # For the editor to keep track of attempted imports self._importAttempts = [] # To keep track of the response for introspection self._currentCTO = None self._currentACO = None # Write buffer to store messages in for writing self._write_buffer = None # Create timer to keep polling any results # todo: Maybe use yoton events to process messages as they arrive. # I tried this briefly, but it seemd to be less efficient because # messages are not so much bach-processed anymore. We should decide # on either method. self._timer = QtCore.QTimer(self) self._timer.setInterval(POLL_TIMER_INTERVAL) # ms self._timer.setSingleShot(False) self._timer.timeout.connect(self.poll) self._timer.start() # Add context menu self._menu = ShellContextMenu(shell=self, parent=self) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(lambda p: self._menu.popup( self.mapToGlobal(p + QtCore.QPoint(0, 3)))) # Start! self.resetVariables() self.connectToKernel(info)
def __init__(self, parent, info): BaseShell.__init__(self, parent) # Get standard info if not given. if info is None and iep.config.shellConfigs2: info = iep.config.shellConfigs2[0] if not info: info = KernelInfo(None) # Store info so we can reuse it on a restart self._info = info # For the editor to keep track of attempted imports self._importAttempts = [] # To keep track of the response for introspection self._currentCTO = None self._currentACO = None # Write buffer to store messages in for writing self._write_buffer = None # Create timer to keep polling any results # todo: Maybe use yoton events to process messages as they arrive. # I tried this briefly, but it seemd to be less efficient because # messages are not so much bach-processed anymore. We should decide # on either method. self._timer = QtCore.QTimer(self) self._timer.setInterval(POLL_TIMER_INTERVAL) # ms self._timer.setSingleShot(False) self._timer.timeout.connect(self.poll) self._timer.start() # Add context menu self._menu = ShellContextMenu(shell=self, parent=self) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(lambda p: self._menu.popup(self.mapToGlobal(p+QtCore.QPoint(0,3)))) # Start! self.resetVariables() self.connectToKernel(info)
class PythonShell(BaseShell): """ The PythonShell class implements the python part of the shell by connecting to a remote process that runs a Python interpreter. """ # Emits when the status string has changed or when receiving a new prompt stateChanged = QtCore.Signal(BaseShell) # Emits when the debug status is changed debugStateChanged = QtCore.Signal(BaseShell) def __init__(self, parent, info): BaseShell.__init__(self, parent) # Get standard info if not given. if info is None and iep.config.shellConfigs2: info = iep.config.shellConfigs2[0] if not info: info = KernelInfo(None) # Store info so we can reuse it on a restart self._info = info # For the editor to keep track of attempted imports self._importAttempts = [] # To keep track of the response for introspection self._currentCTO = None self._currentACO = None # Write buffer to store messages in for writing self._write_buffer = None # Create timer to keep polling any results # todo: Maybe use yoton events to process messages as they arrive. # I tried this briefly, but it seemd to be less efficient because # messages are not so much bach-processed anymore. We should decide # on either method. self._timer = QtCore.QTimer(self) self._timer.setInterval(POLL_TIMER_INTERVAL) # ms self._timer.setSingleShot(False) self._timer.timeout.connect(self.poll) self._timer.start() # Add context menu self._menu = ShellContextMenu(shell=self, parent=self) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(lambda p: self._menu.popup( self.mapToGlobal(p + QtCore.QPoint(0, 3)))) # Start! self.resetVariables() self.connectToKernel(info) def resetVariables(self): """ Resets some variables. """ # Reset read state self.setReadOnly(False) # Variables to store state, python version, builtins and keywords self._state = '' self._debugState = {} self._version = "" self._builtins = [] self._keywords = [] self._startup_info = {} self._start_time = 0 # (re)set import attempts self._importAttempts[:] = [] # Update self.stateChanged.emit(self) def connectToKernel(self, info): """ connectToKernel() Create kernel and connect to it. """ # Create yoton context self._context = ct = yoton.Context() # Create stream channels self._strm_out = yoton.SubChannel(ct, 'strm-out') self._strm_err = yoton.SubChannel(ct, 'strm-err') self._strm_raw = yoton.SubChannel(ct, 'strm-raw') self._strm_echo = yoton.SubChannel(ct, 'strm-echo') self._strm_prompt = yoton.SubChannel(ct, 'strm-prompt') self._strm_broker = yoton.SubChannel(ct, 'strm-broker') self._strm_action = yoton.SubChannel(ct, 'strm-action', yoton.OBJECT) # Set channels to sync mode. This means that if the IEP cannot process # the messages fast enough, the sending side is blocked for a short # while. We don't want our users to miss any messages. for c in [self._strm_out, self._strm_err]: c.set_sync_mode(True) # Create control channels self._ctrl_command = yoton.PubChannel(ct, 'ctrl-command') self._ctrl_code = yoton.PubChannel(ct, 'ctrl-code', yoton.OBJECT) self._ctrl_broker = yoton.PubChannel(ct, 'ctrl-broker') # Create status channels self._stat_interpreter = yoton.StateChannel(ct, 'stat-interpreter') self._stat_debug = yoton.StateChannel(ct, 'stat-debug', yoton.OBJECT) self._stat_startup = yoton.StateChannel(ct, 'stat-startup', yoton.OBJECT) self._stat_startup.received.bind(self._onReceivedStartupInfo) # Create introspection request channel self._request = yoton.ReqChannel(ct, 'reqp-introspect') # Connect! The broker will only start the kernel AFTER # we connect, so we do not miss out on anything. slot = iep.localKernelManager.createKernel(finishKernelInfo(info)) self._brokerConnection = ct.connect('localhost:%i' % slot) self._brokerConnection.closed.bind(self._onConnectionClose) # todo: see polling vs events # # Detect incoming messages # for c in [self._strm_out, self._strm_err, self._strm_raw, # self._strm_echo, self._strm_prompt, self._strm_broker, # self._strm_action, # self._stat_interpreter, self._stat_debug]: # c.received.bind(self.poll) def _onReceivedStartupInfo(self, channel): startup_info = channel.recv() # Store the whole dict self._startup_info = startup_info # Store when we received this self._start_time = time.time() # Set version version = startup_info.get('version', None) if isinstance(version, tuple): version = [str(v) for v in version] self._version = '.'.join(version[:2]) # Set keywords L = startup_info.get('keywords', None) if isinstance(L, list): self._keywords = L # Set builtins L = startup_info.get('builtins', None) if isinstance(L, list): self._builtins = L # Notify self.stateChanged.emit(self) ## Introspection processing methods def processCallTip(self, cto): """ Processes a calltip request using a CallTipObject instance. """ # Try using buffer first (not if we're not the requester) if self is cto.textCtrl: if cto.tryUsingBuffer(): return # Clear buffer to prevent doing a second request # and store cto to see whether the response is still wanted. cto.setBuffer('') self._currentCTO = cto # Post request future = self._request.signature(cto.name) future.add_done_callback(self._processCallTip_response) future.cto = cto def _processCallTip_response(self, future): """ Process response of shell to show signature. """ # Process future if future.cancelled(): #print('Introspect cancelled') # No kernel return elif future.exception(): print('Introspect-exception: ', future.exception()) return else: response = future.result() cto = future.cto # First see if this is still the right editor (can also be a shell) editor1 = iep.editors.getCurrentEditor() editor2 = iep.shells.getCurrentShell() if cto.textCtrl not in [editor1, editor2]: # The editor or shell starting the autocomp is no longer active aco.textCtrl.autocompleteCancel() return # Invalid response if response is None: cto.textCtrl.autocompleteCancel() return # If still required, show tip, otherwise only store result if cto is self._currentCTO: cto.finish(response) else: cto.setBuffer(response) def processAutoComp(self, aco): """ Processes an autocomp request using an AutoCompObject instance. """ # Try using buffer first (not if we're not the requester) if self is aco.textCtrl: if aco.tryUsingBuffer(): return # Include builtins and keywords? if not aco.name: aco.addNames(self._builtins) if iep.config.settings.autoComplete_keywords: aco.addNames(self._keywords) # Set buffer to prevent doing a second request # and store aco to see whether the response is still wanted. aco.setBuffer() self._currentACO = aco # Post request future = self._request.dir(aco.name) future.add_done_callback(self._processAutoComp_response) future.aco = aco def _processAutoComp_response(self, future): """ Process the response of the shell for the auto completion. """ # Process future if future.cancelled(): #print('Introspect cancelled') # No living kernel return elif future.exception(): print('Introspect-exception: ', future.exception()) return else: response = future.result() aco = future.aco # First see if this is still the right editor (can also be a shell) editor1 = iep.editors.getCurrentEditor() editor2 = iep.shells.getCurrentShell() if aco.textCtrl not in [editor1, editor2]: # The editor or shell starting the autocomp is no longer active aco.textCtrl.autocompleteCancel() return # Add result to the list foundNames = [] if response is not None: foundNames = response aco.addNames(foundNames) # Process list if aco.name and not foundNames: # No names found for the requested name. This means # it does not exist, let's try to import it importNames, importLines = iep.parser.getFictiveImports(editor1) baseName = aco.nameInImportNames(importNames) if baseName: line = importLines[baseName].strip() if line not in self._importAttempts: # Do import self.processLine(line + ' # auto-import') self._importAttempts.append(line) # Wait a barely noticable time to increase the chances # That the import is complete when we repost the request. time.sleep(0.2) # To be sure, decrease the experiration date on the buffer aco.setBuffer(timeout=1) # Repost request future = self._request.signature(aco.name) future.add_done_callback(self._processAutoComp_response) future.aco = aco else: # If still required, show list, otherwise only store result if self._currentACO is aco: aco.finish() else: aco.setBuffer() ## Methods for executing code def executeCommand(self, text): """ executeCommand(text) Execute one-line command in the remote Python session. """ self._ctrl_command.send(text) def executeCode(self, text, fname, lineno=0, cellName=None): """ executeCode(text, fname, lineno, cellName=None) Execute (run) a large piece of code in the remote shell. text: the source code to execute filename: the file from which the source comes lineno: the first lineno of the text in the file, where 0 would be the first line of the file... The text (source code) is first pre-processed: - convert all line-endings to \n - remove all empty lines at the end - remove commented lines at the end - convert tabs to spaces - dedent so minimal indentation is zero """ # Convert tabs to spaces text = text.replace("\t", " " * 4) # Make sure there is always *some* text if not text: text = ' ' # Examine the text line by line... # - check for empty/commented lined at the end # - calculate minimal indentation lines = text.splitlines() lastLineOfCode = 0 minIndent = 99 for linenr in range(len(lines)): # Get line line = lines[linenr] # Check if empty (can be commented, but nothing more) tmp = line.split("#", 1)[0] # get part before first # if tmp.count(" ") == len(tmp): continue # empty line, proceed else: lastLineOfCode = linenr # Calculate indentation tmp = line.lstrip(" ") indent = len(line) - len(tmp) if indent < minIndent: minIndent = indent # Copy all proper lines to a new list, # remove minimal indentation, but only if we then would only remove # spaces (in the case of commented lines) lines2 = [] for linenr in range(lastLineOfCode + 1): line = lines[linenr] # Remove indentation, if line[:minIndent].count(" ") == minIndent: line = line[minIndent:] else: line = line.lstrip(" ") lines2.append(line) # Send message text = "\n".join(lines2) msg = { 'source': text, 'fname': fname, 'lineno': lineno, 'cellName': cellName } self._ctrl_code.send(msg) ## The polling methods and terminating methods def poll(self, channel=None): """ poll() To keep the shell up-to-date. Call this periodically. """ if self._write_buffer: # There is still data in the buffer sub, M = self._write_buffer else: # Check what subchannel has the latest message pending sub = yoton.select_sub_channel(self._strm_out, self._strm_err, self._strm_echo, self._strm_raw, self._strm_broker, self._strm_prompt) # Read messages from it if sub: M = sub.recv_selected() #M = [sub.recv()] # Slow version (for testing) # Optimization: handle backspaces on stack of messages if sub is self._strm_out: M = self._handleBackspacesOnList(M) # New prompt? if sub is self._strm_prompt: self.stateChanged.emit(self) # Write all pending messages that are later than any other message if sub: # Select messages to process N = 256 M, buffer = M[:N], M[N:] # Buffer the rest if buffer: self._write_buffer = sub, buffer else: self._write_buffer = None # Get how to deal with prompt prompt = 0 if sub is self._strm_echo: prompt = 1 elif sub is self._strm_prompt: prompt = 2 # Get color color = None if sub is self._strm_broker: color = '#000' elif sub is self._strm_raw: color = '#888888' # Halfway elif sub is self._strm_err: color = '#F00' # Write self.write(''.join(M), prompt, color) # Do any actions? action = self._strm_action.recv(False) if action: if action.startswith('open '): fname = action.split(' ', 1)[1] iep.editors.loadFile(fname) else: print('Unkown action: %s' % action) # Update status state = self._stat_interpreter.recv() if state != self._state: self._state = state self.stateChanged.emit(self) # Update debug status state = self._stat_debug.recv() if state != self._debugState: self._debugState = state self.debugStateChanged.emit(self) def interrupt(self): """ interrupt() Send a Keyboard interrupt signal to the main thread of the remote process. """ self._ctrl_broker.send('INT') def restart(self, scriptFile=None): """ restart(scriptFile=None) Terminate the shell, after which it is restarted. Args can be a filename, to execute as a script as soon as the shell is back up. """ # Get info info = finishKernelInfo(self._info, scriptFile) # Create message and send msg = 'RESTART\n' + ssdf.saves(info) self._ctrl_broker.send(msg) # Reset self.resetVariables() def terminate(self): """ terminate() Terminates the python process. It will first try gently, but if that does not work, the process shall be killed. To be notified of the termination, connect to the "terminated" signal of the shell. """ self._ctrl_broker.send('TERM') def closeShell(self): # do not call it close(); that is a reserved method. """ closeShell() Very simple. This closes the shell. If possible, we will first tell the broker to terminate the kernel. The broker will be cleaned up if there are no clients connected and if there is no active kernel. In a multi-user environment, we should thus be able to close the shell without killing the kernel. But in a closed 1-to-1 environment we really want to prevent loose brokers and kernels dangling around. In both cases however, it is the responsibility of the broker to terminate the kernel, and the shell will simply assume that this will work :) """ # If we can, try to tell the broker to terminate the kernel if self._context and self._context.connection_count: self.terminate() self._context.flush() # Important, make sure the message is send! self._context.close() # Adios iep.shells.removeShell(self) def _onConnectionClose(self, c, why): """ To be called after disconnecting. In general, the broker will not close the connection, so it can be considered an error-state if this function is called. """ # Stop context if self._context: self._context.close() # New (empty prompt) self._cursor1.movePosition(self._cursor1.End, A_MOVE) self._cursor2.movePosition(self._cursor2.End, A_MOVE) self.write('\n\n') self.write('Lost connection with broker:\n') self.write(why) self.write('\n\n') # Set style to indicate dead-ness self.setReadOnly(True) # Goto end such that the closing message is visible cursor = self.textCursor() cursor.movePosition(cursor.End, A_MOVE) self.setTextCursor(cursor) self.ensureCursorVisible()
class PythonShell(BaseShell): """ The PythonShell class implements the python part of the shell by connecting to a remote process that runs a Python interpreter. """ # Emits when the status string has changed or when receiving a new prompt stateChanged = QtCore.Signal(BaseShell) # Emits when the debug status is changed debugStateChanged = QtCore.Signal(BaseShell) def __init__(self, parent, info): BaseShell.__init__(self, parent) # Get standard info if not given. if info is None and iep.config.shellConfigs2: info = iep.config.shellConfigs2[0] if not info: info = KernelInfo(None) # Store info so we can reuse it on a restart self._info = info # For the editor to keep track of attempted imports self._importAttempts = [] # To keep track of the response for introspection self._currentCTO = None self._currentACO = None # Write buffer to store messages in for writing self._write_buffer = None # Create timer to keep polling any results # todo: Maybe use yoton events to process messages as they arrive. # I tried this briefly, but it seemd to be less efficient because # messages are not so much bach-processed anymore. We should decide # on either method. self._timer = QtCore.QTimer(self) self._timer.setInterval(POLL_TIMER_INTERVAL) # ms self._timer.setSingleShot(False) self._timer.timeout.connect(self.poll) self._timer.start() # Add context menu self._menu = ShellContextMenu(shell=self, parent=self) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(lambda p: self._menu.popup(self.mapToGlobal(p+QtCore.QPoint(0,3)))) # Start! self.resetVariables() self.connectToKernel(info) def resetVariables(self): """ Resets some variables. """ # Reset read state self.setReadOnly(False) # Variables to store state, python version, builtins and keywords self._state = '' self._debugState = {} self._version = "" self._builtins = [] self._keywords = [] self._startup_info = {} self._start_time = 0 # (re)set import attempts self._importAttempts[:] = [] # Update self.stateChanged.emit(self) def connectToKernel(self, info): """ connectToKernel() Create kernel and connect to it. """ # Create yoton context self._context = ct = yoton.Context() # Create stream channels self._strm_out = yoton.SubChannel(ct, 'strm-out') self._strm_err = yoton.SubChannel(ct, 'strm-err') self._strm_raw = yoton.SubChannel(ct, 'strm-raw') self._strm_echo = yoton.SubChannel(ct, 'strm-echo') self._strm_prompt = yoton.SubChannel(ct, 'strm-prompt') self._strm_broker = yoton.SubChannel(ct, 'strm-broker') self._strm_action = yoton.SubChannel(ct, 'strm-action', yoton.OBJECT) # Set channels to sync mode. This means that if the IEP cannot process # the messages fast enough, the sending side is blocked for a short # while. We don't want our users to miss any messages. for c in [self._strm_out, self._strm_err]: c.set_sync_mode(True) # Create control channels self._ctrl_command = yoton.PubChannel(ct, 'ctrl-command') self._ctrl_code = yoton.PubChannel(ct, 'ctrl-code', yoton.OBJECT) self._ctrl_broker = yoton.PubChannel(ct, 'ctrl-broker') # Create status channels self._stat_interpreter = yoton.StateChannel(ct, 'stat-interpreter') self._stat_debug = yoton.StateChannel(ct, 'stat-debug', yoton.OBJECT) self._stat_startup = yoton.StateChannel(ct, 'stat-startup', yoton.OBJECT) self._stat_startup.received.bind(self._onReceivedStartupInfo) # Create introspection request channel self._request = yoton.ReqChannel(ct, 'reqp-introspect') # Connect! The broker will only start the kernel AFTER # we connect, so we do not miss out on anything. slot = iep.localKernelManager.createKernel(finishKernelInfo(info)) self._brokerConnection = ct.connect('localhost:%i'%slot) self._brokerConnection.closed.bind(self._onConnectionClose) # todo: see polling vs events # # Detect incoming messages # for c in [self._strm_out, self._strm_err, self._strm_raw, # self._strm_echo, self._strm_prompt, self._strm_broker, # self._strm_action, # self._stat_interpreter, self._stat_debug]: # c.received.bind(self.poll) def _onReceivedStartupInfo(self, channel): startup_info = channel.recv() # Store the whole dict self._startup_info = startup_info # Store when we received this self._start_time = time.time() # Set version version = startup_info.get('version', None) if isinstance(version, tuple): version = [str(v) for v in version] self._version = '.'.join(version[:2]) # Set keywords L = startup_info.get('keywords', None) if isinstance(L, list): self._keywords = L # Set builtins L = startup_info.get('builtins', None) if isinstance(L, list): self._builtins = L # Notify self.stateChanged.emit(self) ## Introspection processing methods def processCallTip(self, cto): """ Processes a calltip request using a CallTipObject instance. """ # Try using buffer first (not if we're not the requester) if self is cto.textCtrl: if cto.tryUsingBuffer(): return # Clear buffer to prevent doing a second request # and store cto to see whether the response is still wanted. cto.setBuffer('') self._currentCTO = cto # Post request future = self._request.signature(cto.name) future.add_done_callback(self._processCallTip_response) future.cto = cto def _processCallTip_response(self, future): """ Process response of shell to show signature. """ # Process future if future.cancelled(): #print('Introspect cancelled') # No kernel return elif future.exception(): print('Introspect-exception: ', future.exception()) return else: response = future.result() cto = future.cto # First see if this is still the right editor (can also be a shell) editor1 = iep.editors.getCurrentEditor() editor2 = iep.shells.getCurrentShell() if cto.textCtrl not in [editor1, editor2]: # The editor or shell starting the autocomp is no longer active aco.textCtrl.autocompleteCancel() return # Invalid response if response is None: cto.textCtrl.autocompleteCancel() return # If still required, show tip, otherwise only store result if cto is self._currentCTO: cto.finish(response) else: cto.setBuffer(response) def processAutoComp(self, aco): """ Processes an autocomp request using an AutoCompObject instance. """ # Try using buffer first (not if we're not the requester) if self is aco.textCtrl: if aco.tryUsingBuffer(): return # Include builtins and keywords? if not aco.name: aco.addNames(self._builtins) if iep.config.settings.autoComplete_keywords: aco.addNames(self._keywords) # Set buffer to prevent doing a second request # and store aco to see whether the response is still wanted. aco.setBuffer() self._currentACO = aco # Post request future = self._request.dir(aco.name) future.add_done_callback(self._processAutoComp_response) future.aco = aco def _processAutoComp_response(self, future): """ Process the response of the shell for the auto completion. """ # Process future if future.cancelled(): #print('Introspect cancelled') # No living kernel return elif future.exception(): print('Introspect-exception: ', future.exception()) return else: response = future.result() aco = future.aco # First see if this is still the right editor (can also be a shell) editor1 = iep.editors.getCurrentEditor() editor2 = iep.shells.getCurrentShell() if aco.textCtrl not in [editor1, editor2]: # The editor or shell starting the autocomp is no longer active aco.textCtrl.autocompleteCancel() return # Add result to the list foundNames = [] if response is not None: foundNames = response aco.addNames(foundNames) # Process list if aco.name and not foundNames: # No names found for the requested name. This means # it does not exist, let's try to import it importNames, importLines = iep.parser.getFictiveImports(editor1) baseName = aco.nameInImportNames(importNames) if baseName: line = importLines[baseName].strip() if line not in self._importAttempts: # Do import self.processLine(line + ' # auto-import') self._importAttempts.append(line) # Wait a barely noticable time to increase the chances # That the import is complete when we repost the request. time.sleep(0.2) # To be sure, decrease the experiration date on the buffer aco.setBuffer(timeout=1) # Repost request future = self._request.signature(aco.name) future.add_done_callback(self._processAutoComp_response) future.aco = aco else: # If still required, show list, otherwise only store result if self._currentACO is aco: aco.finish() else: aco.setBuffer() ## Methods for executing code def executeCommand(self, text): """ executeCommand(text) Execute one-line command in the remote Python session. """ self._ctrl_command.send(text) def executeCode(self, text, fname, lineno=0, cellName=None): """ executeCode(text, fname, lineno, cellName=None) Execute (run) a large piece of code in the remote shell. text: the source code to execute filename: the file from which the source comes lineno: the first lineno of the text in the file, where 0 would be the first line of the file... The text (source code) is first pre-processed: - convert all line-endings to \n - remove all empty lines at the end - remove commented lines at the end - convert tabs to spaces - dedent so minimal indentation is zero """ # Convert tabs to spaces text = text.replace("\t"," "*4) # Make sure there is always *some* text if not text: text = ' ' # Examine the text line by line... # - check for empty/commented lined at the end # - calculate minimal indentation lines = text.splitlines() lastLineOfCode = 0 minIndent = 99 for linenr in range(len(lines)): # Get line line = lines[linenr] # Check if empty (can be commented, but nothing more) tmp = line.split("#",1)[0] # get part before first # if tmp.count(" ") == len(tmp): continue # empty line, proceed else: lastLineOfCode = linenr # Calculate indentation tmp = line.lstrip(" ") indent = len(line) - len(tmp) if indent < minIndent: minIndent = indent # Copy all proper lines to a new list, # remove minimal indentation, but only if we then would only remove # spaces (in the case of commented lines) lines2 = [] for linenr in range(lastLineOfCode+1): line = lines[linenr] # Remove indentation, if line[:minIndent].count(" ") == minIndent: line = line[minIndent:] else: line = line.lstrip(" ") lines2.append( line ) # Send message text = "\n".join(lines2) msg = {'source':text, 'fname':fname, 'lineno':lineno, 'cellName': cellName} self._ctrl_code.send(msg) ## The polling methods and terminating methods def poll(self, channel=None): """ poll() To keep the shell up-to-date. Call this periodically. """ if self._write_buffer: # There is still data in the buffer sub, M = self._write_buffer else: # Check what subchannel has the latest message pending sub = yoton.select_sub_channel(self._strm_out, self._strm_err, self._strm_echo, self._strm_raw, self._strm_broker, self._strm_prompt ) # Read messages from it if sub: M = sub.recv_selected() #M = [sub.recv()] # Slow version (for testing) # Optimization: handle backspaces on stack of messages if sub is self._strm_out: M = self._handleBackspacesOnList(M) # New prompt? if sub is self._strm_prompt: self.stateChanged.emit(self) # Write all pending messages that are later than any other message if sub: # Select messages to process N = 256 M, buffer = M[:N], M[N:] # Buffer the rest if buffer: self._write_buffer = sub, buffer else: self._write_buffer = None # Get how to deal with prompt prompt = 0 if sub is self._strm_echo: prompt = 1 elif sub is self._strm_prompt: prompt = 2 # Get color color = None if sub is self._strm_broker: color = '#000' elif sub is self._strm_raw: color = '#888888' # Halfway elif sub is self._strm_err: color = '#F00' # Write self.write(''.join(M), prompt, color) # Do any actions? action = self._strm_action.recv(False) if action: if action.startswith('open '): fname = action.split(' ',1)[1] iep.editors.loadFile(fname) else: print('Unkown action: %s' % action) # Update status state = self._stat_interpreter.recv() if state != self._state: self._state = state self.stateChanged.emit(self) # Update debug status state = self._stat_debug.recv() if state != self._debugState: self._debugState = state self.debugStateChanged.emit(self) def interrupt(self): """ interrupt() Send a Keyboard interrupt signal to the main thread of the remote process. """ self._ctrl_broker.send('INT') def restart(self, scriptFile=None): """ restart(scriptFile=None) Terminate the shell, after which it is restarted. Args can be a filename, to execute as a script as soon as the shell is back up. """ # Get info info = finishKernelInfo(self._info, scriptFile) # Create message and send msg = 'RESTART\n' + ssdf.saves(info) self._ctrl_broker.send(msg) # Reset self.resetVariables() def terminate(self): """ terminate() Terminates the python process. It will first try gently, but if that does not work, the process shall be killed. To be notified of the termination, connect to the "terminated" signal of the shell. """ self._ctrl_broker.send('TERM') def closeShell(self): # do not call it close(); that is a reserved method. """ closeShell() Very simple. This closes the shell. If possible, we will first tell the broker to terminate the kernel. The broker will be cleaned up if there are no clients connected and if there is no active kernel. In a multi-user environment, we should thus be able to close the shell without killing the kernel. But in a closed 1-to-1 environment we really want to prevent loose brokers and kernels dangling around. In both cases however, it is the responsibility of the broker to terminate the kernel, and the shell will simply assume that this will work :) """ # If we can, try to tell the broker to terminate the kernel if self._context and self._context.connection_count: self.terminate() self._context.flush() # Important, make sure the message is send! self._context.close() # Adios iep.shells.removeShell(self) def _onConnectionClose(self, c, why): """ To be called after disconnecting. In general, the broker will not close the connection, so it can be considered an error-state if this function is called. """ # Stop context if self._context: self._context.close() # New (empty prompt) self._cursor1.movePosition(self._cursor1.End, A_MOVE) self._cursor2.movePosition(self._cursor2.End, A_MOVE) self.write('\n\n'); self.write('Lost connection with broker:\n') self.write(why) self.write('\n\n') # Set style to indicate dead-ness self.setReadOnly(True) # Goto end such that the closing message is visible cursor = self.textCursor() cursor.movePosition(cursor.End, A_MOVE) self.setTextCursor(cursor) self.ensureCursorVisible()