Consola gráfica Python con resaltado de sintaxis – Módulo code + PyQt 4 (+ Scintilla)



Versión: 2.7

Este código de fuente puede utilizarse con varios objetivos. Trata de una consola gráfica de Python, similar a la integrada con IDLE, que hace uso del módulo code para ejecutar el código, PyQt para el diseño de la interfaz gráfica junto con QScintilla, una portación del control para edición de código Scintilla. A través del syntax highlighting pretende proveer una simple consola de mayor comodidad en comparación a la que se distribuye junto con CPython. Podrá ser de utilidad para aquellos usuarios que deseen explorar código en acción de PyQt, el control QScintilla y el módulo estándar code.

A lo largo del código podrán observar los múltiples comentarios que espero que les sean de utilidad para su comprensión.

El código requiere la instalación del paquete PyQt 4 y la versión de Python 2.7. Para aquellos usuarios de Windows que quieran ejecutar el script sin los requerimientos anteriores pueden descargar el archivo ejecutable desde aquí.

Vista previa

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#       main.py
#       
#       Copyright 2013 Recursos Python - www.recursospython.com
#       

import sys

from code import InteractiveConsole
from threading import Thread
from time import sleep

from PyQt4.QtCore import QObject, QRect, Qt, pyqtSignal
from PyQt4.QtGui import QApplication, QMainWindow
from PyQt4.Qsci import QsciScintilla, QsciLexerPython


class GUIConsole(Thread, InteractiveConsole):
    
    def __init__(self, scintilla):
        Thread.__init__(self)
        InteractiveConsole.__init__(self)
        self.scintilla = scintilla
    
    def write(self, string):
        """
        Enviar el output al editor en lugar de
        la consola.
        """
        self.scintilla.append(string)
    
    def raw_input(self, prompt_unused):
        """
        Leer la entrada desde el editor en lugar
        de la consola.
        """
        self.scintilla.append(">>> ")  # Ignorar el argumento
        self.scintilla.SendScintilla(
            self.scintilla.SCI_GOTOPOS, len(self.scintilla.text())
        )
        return self.scintilla.get_input()
    
    def run(self):
        try:
            self.interact()
        except AttributeError:
            # Usamos AttributeError para terminar el thread
            pass


class SyntaxHighliter(QsciScintilla):
    """
    Heredamos de QsciScintilla para detectar la
    presión de teclas y retornar la línea actual.
    """
    
    def __init__(self, parent):
        QsciScintilla.__init__(self, parent)
        self._output_data = None
        self._run = True
    
    def keyPressEvent(self, event):
        # Tecla presionada
        key = event.key()
        # Posición actual
        current_pos = self.SendScintilla(self.SCI_GETCURRENTPOS)
        # Tamaño del contenido
        length = len(self.text())
        # Contenido de la última línea
        last_line_content = self.text(
            self.SendScintilla(self.SCI_LINEFROMPOSITION, length)
        )
        
        # Las teclas arriba, abajo, derecha e izquierda están permitidas
        if not (key == Qt.Key_Up or key == Qt.Key_Down or
                key == Qt.Key_Left or key == Qt.Key_Right):
            # Chequear que se pueda escribir en la posición actual
            if (length - current_pos - len(last_line_content)) >= 0:
                return
        
        # Tecla enter (más la del teclado numérico)
        if key == Qt.Key_Enter or key == Qt.Key_Return:
            # Contenido de la línea actual
            self._output_data = self.text(
                self.SendScintilla(self.SCI_LINEFROMPOSITION, length)
            )
        
        # Llamar a la función original
        QsciScintilla.keyPressEvent(self, event)
    
    def get_input(self):
        # Esperar hasta que se llene el búfer
        while self._output_data is None:
            if not self._run:
                # Lanzar AttributeError
                return
            sleep(0.01)
        # Remueve el banner ">>> " y convierte los datos
        output = unicode(self._output_data[4:])
        # Resetear el búfer
        self._output_data = None
        return output
    
    def stop(self):
        self._run = False


class Window(QMainWindow):
    
    def __init__(self):
        # Inicializar la clase padre
        QMainWindow.__init__(self)
        
        # Tamaño y título
        self.resize(500, 300)
        self.setWindowTitle("Consola Recursos Python")
        
        # Editor - resaltador de sintaxis
        self.scintilla = SyntaxHighliter(self)
        self.scintilla.setGeometry(QRect(10, 10, 480, 250))
        self.scintilla.setLexer(QsciLexerPython())
        
        # Ejecutar la consola en un nuevo hilo
        self.console = GUIConsole(self.scintilla)
        self.console.start()
        
        # Ajustar el editor a la ventana
        self.setCentralWidget(self.scintilla)
        
        # Cambiar el output
        self.original_stdout = sys.stdout
        sys.stdout = self
    
    def write(self, string):
        self.console.write(string)
    
    def closeEvent(self, event):
        """Terminar el thread y establecer el stdout original"""
        sys.stdout = self.original_stdout
        self.scintilla.stop()
        QMainWindow.closeEvent(self, event)


if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec_()



Deja una respuesta