Example #1
0
File: code.py Project: Asan96/car
    def __init__(self):
        super().__init__()

        self.setEolMode(self.SC_EOL_LF)    # 以\n换行
        self.setWrapMode(self.WrapWord)    # 自动换行。self.WrapWord是父类QsciScintilla的
        self.setAutoCompletionSource(self.AcsAll)  # 自动补全。对于所有Ascii字符
        self.setAutoCompletionCaseSensitivity(False)  # 自动补全大小写敏感
        self.setAutoCompletionThreshold(1)  # 输入多少个字符才弹出补全提示
        self.setFolding(True)  # 代码可折叠
        self.setTabWidth(4)  # 设置缩进长度
        self.setUtf8(True)  # 设置文档的编码格式为 “utf8”
        self.setIndentationGuides(True)  # 设置缩进参考线
        self.setAutoIndent(True)  # 设置自动缩进
        self.setCaretLineVisible(True)
        self.lexer = LexerPython(self)  # 语法高亮显示
        self.setLexer(self.lexer)
        self.setCaretLineVisible(True)  # 是否高亮显示光标所在行
        self.setCaretLineBackgroundColor(QtGui.QColor(250, 244, 217))  # 光标所在行的底色
        self.setIndentationsUseTabs(True)  # 设置使用Tabs缩进
        # self.setFont(QtGui.QFont('Consolas', 20))  # 设置默认字体
        self.setMarginType(0, self.NumberMargin)    # 0~4。第0个左边栏显示行号
        self.setMarginsBackgroundColor(QtGui.QColor(255, 255, 255))  # 边栏背景颜色
        # self.setScrollWidthTracking(True)
        # self.setScrollWidth(500)
        self.setMarginWidth(0, 40)  # 边栏宽度
        self.setAutoIndent(True)  # 换行后自动缩进
        self.__api = QsciAPIs(self.lexer)
        for kw in keyword.kwlist:
            self.__api.add(kw)
        self.__api.prepare()
        self.setText("#!/usr/bin/python \n# coding=utf-8\n# version: Python3\n# please edit your code here:\n")
Example #2
0
    def setPythonAutocomplete(self):

        self.autocomplete = QsciAPIs(self.lexer)
        self.keywords = self.lexer.keywords(1)

        self.keywords = self.keywords.split(' ')

        for word in self.keywords:
            self.autocomplete.add(word)

        self.autocomplete.add('super')
        self.autocomplete.add('self')
        self.autocomplete.add('__name__')
        self.autocomplete.add('__main__')
        self.autocomplete.add('__init__')
        self.autocomplete.add('__str__')
        self.autocomplete.add('__repr__')

        self.autocomplete.prepare()

        ## Set the length of the string before the editor tries to autocomplete
        self.setAutoCompletionThreshold(3)

        ## Tell the editor we are using a QsciAPI for the autocompletion
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)

        self.updateAutoComplete()
Example #3
0
    def __init__(self, language, forPreparation=False, parent=None):
        """
        Constructor
        
        @param language language of the APIs object (string)
        @param forPreparation flag indicating this object is just needed
            for a preparation process (boolean)
        @param parent reference to the parent object (QObject)
        """
        super(APIs, self).__init__(parent)
        self.setObjectName("APIs_{0}".format(language))

        self.__inPreparation = False
        self.__language = language
        self.__forPreparation = forPreparation
        self.__lexer = Lexers.getLexer(self.__language)
        self.__apifiles = Preferences.getEditorAPI(self.__language)
        self.__apifiles.sort()
        if self.__lexer is None:
            self.__apis = None
        else:
            self.__apis = QsciAPIs(self.__lexer)
            self.__apis.apiPreparationFinished.connect(
                self.__apiPreparationFinished)
            self.__apis.apiPreparationCancelled.connect(
                self.__apiPreparationCancelled)
            self.__apis.apiPreparationStarted.connect(
                self.__apiPreparationStarted)
            self.__loadAPIs()
Example #4
0
    def set_theme(self, theme=DayTheme):
        """
        Connect the theme to a lexer and return the lexer for the editor to
        apply to the script text.
        """
        self.lexer = PythonLexer()
        theme.apply_to(self.lexer)
        self.lexer.setDefaultPaper(theme.Paper)
        self.setCaretForegroundColor(theme.Caret)
        self.setMarginsBackgroundColor(theme.Margin)
        self.setMarginsForegroundColor(theme.Caret)
        self.setIndicatorForegroundColor(theme.IndicatorError,
                                         self.indicators['error']['id'])
        self.setIndicatorForegroundColor(theme.IndicatorStyle,
                                         self.indicators['style']['id'])
        self.setMarkerBackgroundColor(theme.IndicatorError, self.MARKER_NUMBER)

        api = QsciAPIs(self.lexer)
        for entry in self.api:
            api.add(entry)
        api.prepare()
        self.setAutoCompletionThreshold(2)
        self.setAutoCompletionSource(QsciScintilla.AcsAll)

        self.setLexer(self.lexer)
Example #5
0
File: editor.py Project: zencuke/mu
 def set_api(self, api_definitions):
     """
     Sets the API entries for tooltips, calltips and the like.
     """
     self.api = QsciAPIs(self.lexer)
     for entry in api_definitions:
         self.api.add(entry)
     self.api.prepare()
Example #6
0
 def __init__(self):
     self.lexer = QsciLexerPython()
     self.apis = QsciAPIs(self.lexer)
     self.lexer.setAPIs(self.apis)
     self.apis.apiPreparationFinished.connect(
         self.slotApiPreparationFinished)
     for fn in API_FILES:
         ok = self.apis.load(PATH + '/api_raw/' + fn)
     self.apis.prepare()
Example #7
0
 def _init_apis(self):
     """
     加载自定义智能提示文件
     Returns:
     """
     self._apis = QsciAPIs(self._lexer)
     for path in Path(os.path.dirname(__file__)).rglob('*.api'):
         self._apis.load(str(path.absolute()))
     self._apis.prepare()
    def __init__(self, parent=None):
        super(panelCodigo, self).__init__(parent)
        # Definir propiedades graficas
        fuente = QFont()
        fuente.setFamily('Roboto')
        fuente.setFixedPitch(True)
        fuente.setPointSize(8)
        self.setFont(fuente)
        self.setMarginsFont(fuente)
        fuenteEditor = QFontMetrics(fuente)
        self.setMarginsFont(fuente)
        self.setMarginWidth(0, fuenteEditor.width("00000") - 15)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QColor("#E0E0E0"))
        self.setMarginSensitivity(1, True)
        # Ocultar scrollbar inferior
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
        # Dimension del panel donde se edita el codigo
        self.setMinimumSize(600, 360)
        # funcion(señal) para la linea marcada
        self.marginClicked.connect(self.asignarBreak)
        # Icono que se mostrara en la linea marcada
        self.markerDefine(QsciScintilla.Rectangle, self.CONTROL_LINEA_MARCADA)
        self.setMarkerBackgroundColor(QColor("#F90000"),
                                      self.CONTROL_LINEA_MARCADA)
        # Dar valor a los caracteres especiales
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Identificar la linea donde se encuentra la ejecucion
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QColor("#00ff22"))

        # Funciones para identar las lineas del editor de codigo
        self.setAutoIndent(True)
        self.setIndentationGuides(True)
        self.setIndentationsUseTabs(True)
        self.setIndentationWidth(4)

        # Resaltado y fuente del lexer
        self.lexer = QsciLexerPascal()
        self.lexer.setDefaultFont(fuente)

        # Autocompletar haciendo uso de la API Qsci
        api = QsciAPIs(self.lexer)
        self.palabrasGramatica(api)  #flta
        api.prepare()
        # Asignar colores a las keywords de la gramatica
        self.asignarColores()  #falta
        self.setLexer(self.lexer)
        self.setAutoCompletionThreshold(1)
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)

        # Lineas marcadas
        self.controlLineasM = []
Example #9
0
    def _initCodeEdit(self):
        # 初始化编辑器的工作
        self.codeEdit.setUtf8(True)
        self.codeEdit.linesChanged.connect(self.onLinesChanged)  # 行改变
        # 代码高亮
        self.codeEdit.setLexer(QsciLexerJavaScript(self))
        # 自动折叠
        self.codeEdit.setMarginType(3, QsciScintilla.SymbolMargin)
        self.codeEdit.setMarginLineNumbers(3, False)
        self.codeEdit.setMarginWidth(3, 15)
        self.codeEdit.setMarginSensitivity(3, True)
        # 显示行号
        #self.codeEdit.setMarginType(0, QsciScintilla.NumberMargin)
        self.codeEdit.setMarginLineNumbers(0, True)
        self.onLinesChanged()
        # 代码提示
        sciApi = QsciAPIs(self.codeEdit.lexer())
        sciApi.prepare()
        self.codeEdit.setAutoCompletionSource(QsciScintilla.AcsAll)  # 设置源
        self.codeEdit.setAutoCompletionCaseSensitivity(True)  # 设置自动补全大小写敏感
        self.codeEdit.setAutoCompletionThreshold(1)  # 设置每输入一个字符就会出现自动补全的提示
        # 设置字体
        self.codeEdit.setFont(QFont('Consolas', 16))
        self.codeEdit.setMarginsFont(self.codeEdit.font())
        # 设置编码
        self.codeEdit.SendScintilla(QsciScintilla.SCI_SETCODEPAGE,
                                    QsciScintilla.SC_CP_UTF8)

        self.codeEdit.setBraceMatching(QsciScintilla.StrictBraceMatch)

        # 设置当前行高亮
        self.codeEdit.setCaretLineVisible(True)
        self.codeEdit.setCaretLineBackgroundColor(Qt.lightGray)
        self.codeEdit.setCaretForegroundColor(Qt.white)

        # tab
        # table relative
        self.codeEdit.setIndentationsUseTabs(True)
        self.codeEdit.setIndentationWidth(4)
        self.codeEdit.setTabIndents(True)
        self.codeEdit.setAutoIndent(True)
        self.codeEdit.setBackspaceUnindents(True)
        self.codeEdit.setTabWidth(4)

        # indentation guides
        self.codeEdit.setIndentationGuides(True)

        # folding margin
        self.codeEdit.setFolding(QsciScintilla.PlainFoldStyle)
        self.codeEdit.setMarginWidth(2, 12)

        # 自动换行
        self.codeEdit.setWrapMode(QsciScintilla.WrapWord)
Example #10
0
    def setPythonAutocomplete(self):
        self.autocomplete = QsciAPIs(self.lexer)
        self.keywords = wordList

        for word in self.keywords:
            self.autocomplete.add(word)

        self.setAutoCompletionThreshold(2)

        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
        self.updateAutoComplete(self.parent.fileName)

        self.autocomplete.prepare()
Example #11
0
class Main():
    def __init__(self):
        self.lexer = QsciLexerPython()
        self.apis = QsciAPIs(self.lexer)
        self.lexer.setAPIs(self.apis)
        self.apis.apiPreparationFinished.connect(
            self.slotApiPreparationFinished)
        for fn in API_FILES:
            ok = self.apis.load(PATH + '/api_raw/' + fn)
        self.apis.prepare()

    def slotApiPreparationFinished(self):
        self.apis.savePrepared(PATH + '/prepared.api')
        print('Done.')
        QApplication.quit()
Example #12
0
 def __init__(self, language, forPreparation=False, parent=None):
     """
     Constructor
     
     @param language language of the APIs object (string)
     @param forPreparation flag indicating this object is just needed
         for a preparation process (boolean)
     @param parent reference to the parent object (QObject)
     """
     super(APIs, self).__init__(parent)
     self.setObjectName("APIs_{0}".format(language))
     
     self.__inPreparation = False
     self.__language = language
     self.__forPreparation = forPreparation
     self.__lexer = Lexers.getLexer(self.__language)
     self.__apifiles = Preferences.getEditorAPI(self.__language)
     self.__apifiles.sort()
     if self.__lexer is None:
         self.__apis = None
     else:
         self.__apis = QsciAPIs(self.__lexer)
         self.__apis.apiPreparationFinished.connect(
             self.__apiPreparationFinished)
         self.__apis.apiPreparationCancelled.connect(
             self.__apiPreparationCancelled)
         self.__apis.apiPreparationStarted.connect(
             self.__apiPreparationStarted)
         self.__loadAPIs()
Example #13
0
 def set_api(self, api_definitions):
     """
     Sets the API entries for tooltips, calltips and the like.
     """
     self.api = QsciAPIs(self.lexer)
     for entry in api_definitions:
         self.api.add(entry)
     self.api.prepare()
Example #14
0
    def _init_apis(self) -> None:
        """
        加载自定义智能提示文件

        :return: None
        """
        self._apis = QsciAPIs(self._lexer)
        # for path in Path(os.path.join(os.path.dirname(__file__), 'api')).rglob('*.api'):
        #     logger.info('load %s' % str(path.absolute()))
        #     self._apis.load(str(path.absolute()))
        try:
            # 添加额外关键词
            for word in self._parent.keywords():
                self._apis.add(word)
        except Exception as e:
            logger.warning(str(e))
        self._apis.prepare()
Example #15
0
 def __init__(self, parent=None):
     QsciScintilla.__init__(self, parent)
     self.complete = AutoComplete()
     self.complete.prepare.connect(self.update_completes)
     self.lexer = text_lexer(self)
     self.setLexer(self.lexer)
     self.api = QsciAPIs(self.lexer)
     self.setAutoIndent(True)
     self.setMarginLineNumbers(0, True)
     self.setEdgeMode(QsciScintilla.EdgeLine)
     self.setEdgeColumn(79)
     self.setEdgeColor(QColor(0, 0, 0))
     """
     self.setIndentationsUseTabs(True)
     self.setIndentationWidth(get_configs()['tab_width'])
     self.setTabWidth(get_configs()['tab_width'])
     self.setTabIndents(get_configs()['indent_with_tabs'])
     self.setBackspaceUnindents(True)
     self.setCaretLineVisible(True)
     self.setIndentationGuides(True)
     self.setCaretForegroundColor(QColor(get_configs()['cursor_color']))
     self.setCaretLineBackgroundColor(QColor(get_configs()['line_background_color']))
     self.setCaretWidth(6)
     self.setWrapMode(QsciScintilla.WrapNone if not get_configs()['text_wrap'] else QsciScintilla.WrapWhitespace)
     self.setEolMode(get_eol_mode(get_configs()['eol_mode']))
     # self.setMarginsForegroundColor(QColor("#ff888888"))
     """
     self.setMarginWidth(0, len(str(len(self.text().split('\n')))) * 20)
     self.setFolding(QsciScintilla.PlainFoldStyle)
     self.setAutoCompletionSource(QsciScintilla.AcsAll)
     self.setAutoCompletionCaseSensitivity(True)
     self.setAutoCompletionReplaceWord(True)
     self.autoCompleteFromAll()
     self.setAutoCompletionThreshold(1)
     self.setAutoCompletionSource(QsciScintilla.AcsAll)
     self.setUtf8(True)
     self.setBraceMatching(QsciScintilla.StrictBraceMatch)
     self.setMatchedBraceForegroundColor(
         QColor(self.lexer.styles[Name.Decorator]))
     self.setMatchedBraceBackgroundColor(RGB(0, 255, 0).to_pyqt_color())
     self.setCallTipsVisible(-1)
Example #16
0
 def __cCompletion(self):
     r"""
         C自动补全
     :return:
     """
     c_keywords = [
         "char", "double", "enum", "float", "int", "long", "short",
         "signed", "struct", "union", "unsigned", "void", "for", "do",
         "while", "break", "continue", "if", "else", "goto", "switch",
         "case", "default", "return", "auto", "extern", "register",
         "static", "const", "sizeof", "typedef", "volatile"
     ]
     try:
         if isinstance(self.api, QsciAPIs):
             del self.api
     except:
         pass
     self.api = QsciAPIs(self.lxr)
     for kw in c_keywords:
         self.api.add(kw)
     self.api.prepare()
Example #17
0
    def set_theme(self, theme=DayTheme):
        """
        Connect the theme to a lexer and return the lexer for the editor to
        apply to the script text.
        """
        theme.apply_to(self.lexer)
        self.lexer.setDefaultPaper(theme.Paper)
        self.setCaretForegroundColor(theme.Caret)
        self.setMarginsBackgroundColor(theme.Margin)
        self.setMarginsForegroundColor(theme.Caret)
        self.setIndicatorForegroundColor(theme.IndicatorError,
                                         self.check_indicators['error']['id'])
        self.setIndicatorForegroundColor(theme.IndicatorStyle,
                                         self.check_indicators['style']['id'])
        for type_ in self.search_indicators:
            self.setIndicatorForegroundColor(
                theme.IndicatorWordMatch, self.search_indicators[type_]['id'])
        self.setMarkerBackgroundColor(theme.IndicatorError, self.MARKER_NUMBER)

        api = QsciAPIs(self.lexer)
        for entry in self.api:
            api.add(entry)
        api.prepare()
        self.setAutoCompletionThreshold(2)
        self.setAutoCompletionSource(QsciScintilla.AcsAll)

        self.setLexer(self.lexer)
Example #18
0
    def lock(self):
        """
        Sets the default properties for the Python lexer.
        """
        # Lexer Initialization
        lexer = QsciLexerPython(self.ui.code_editor)
        lexer.setDefaultFont(self.font)
        self.ui.code_editor.setLexer(lexer)

        # Auto Completion
        api = QsciAPIs(lexer)
        for var in dir(builtins):
            if not (var[0] == "_"):
                api.add(var)
        api.prepare()

        self.ui.code_editor.setAutoCompletionThreshold(1)
        self.ui.code_editor.setAutoCompletionSource(QsciScintilla.AcsAPIs)

        # Auto Indentation
        self.ui.code_editor.setAutoIndent(True)
        self.ui.code_editor.setIndentationGuides(True)
        self.ui.code_editor.setIndentationsUseTabs(True)
        self.ui.code_editor.setIndentationWidth(4)

        # Font Settings
        font_metrics = QFontMetrics(self.font)
        self.ui.code_editor.setMinimumSize(int(font_metrics.width("0" * 80)),
                                           0)
Example #19
0
 def __pythonCompletion(self):
     r"""
         python 自动补全
     :return:
     """
     python_keywords = [
         "False", "None", "True", "and", "as", "assert", "break", "class",
         "continue", "def", "del", "elif", "else", "except", "finally",
         "for", "from", "global", "if", "import", "in", "is", "isinstance",
         "print", "len", "range", "enumerate", "input", "int", "float",
         "bool", "lambda", "nonlocal", "not", "or", "pass", "raise",
         "return", "try", "while", "with", "yield", "next", "iter"
     ]
     try:
         if isinstance(self.api, QsciAPIs):
             del self.api
     except:
         pass
     self.api = QsciAPIs(self.lxr)
     for kw in python_keywords:
         self.api.add(kw)
     self.api.prepare()
Example #20
0
    def set_theme(self, theme=DayTheme):
        """
        Connect the theme to a lexer and return the lexer for the editor to
        apply to the script text.
        """
        self.lexer = PythonLexer()
        theme.apply_to(self.lexer)
        self.lexer.setDefaultPaper(theme.Paper)
        self.setCaretForegroundColor(theme.Caret)
        self.setMarginsBackgroundColor(theme.Margin)
        self.setMarginsForegroundColor(theme.Caret)
        self.setIndicatorForegroundColor(theme.Indicator)
        self.setMarkerBackgroundColor(theme.Indicator, self.MARKER_NUMBER)

        api = QsciAPIs(self.lexer)
        for entry in self.api:
            api.add(entry)
        api.prepare()
        self.setAutoCompletionThreshold(2)
        self.setAutoCompletionSource(QsciScintilla.AcsAll)

        self.setLexer(self.lexer)
Example #21
0
    def initialize_editor(self):



        self.editor = QsciScintilla()

#        self.editor.cursorPositionChanged.connect(self.e)
#        self.editor.copyAvailable.connect(self.e)
#        self.editor.indicatorClicked.connect(self.e)
#        self.editor.indicatorReleased.connect(self.e)
#        self.editor.linesChanged.connect(self.e)
#        self.editor.marginClicked.connect(self.e)
#        self.editor.modificationAttempted.connect(self.e)
#        self.editor.modificationChanged.connect(self.e)
#        self.editor.selectionChanged.connect(self.e)
#        self.editor.textChanged.connect(self.e)
#        self.editor.userListActivated.connect(self.e)

        if self.editor.__class__.__name__ == "LineTextWidget":
            return  # When using PySide without QSciScintilla

        # define the font to use
        font = QFont()
        font.setFamily("Consolas")
        font.setFixedPitch(True)
        font.setPointSize(10)
        # the font metrics here will help
        # building the margin width later
        fm = QFontMetrics(font)

        # set the default font of the self.editor
        # and take the same font for line numbers
        self.editor.setFont(font)
        self.editor.setMarginsFont(font)

        # Line numbers
        # conventionnaly, margin 0 is for line numbers
        self.editor.setMarginWidth(0, fm.width("00000") + 5)
        self.editor.setMarginLineNumbers(0, True)

        self.editor.setTabWidth(4)

        # Folding visual : we will use boxes
        self.editor.setFolding(QsciScintilla.BoxedTreeFoldStyle)

        self.editor.setAutoIndent(True)

        # Braces matching
        self.editor.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Editing line color
        self.editor.setCaretLineVisible(True)
        self.editor.setCaretLineBackgroundColor(QColor("#CDA869"))

        # Margins colors
        # line numbers margin
        self.editor.setMarginsBackgroundColor(QColor("#333333"))
        self.editor.setMarginsForegroundColor(QColor("#CCCCCC"))

        # folding margin colors (foreground,background)
        self.editor.setFoldMarginColors(QColor("#99CC66"), QColor("#333300"))

        # Choose a lexer
        self.lexer = QsciLexerPython()
        self.lexer.setDefaultFont(font)

        # Set the length of the string before the editor tries to autocomplete
        # In practise this would be higher than 1
        # But its set lower here to make the autocompletion more obvious
        self.editor.setAutoCompletionThreshold(1)
        # Tell the editor we are using a QsciAPI for the autocompletion
        self.editor.setAutoCompletionSource(QsciScintilla.AcsAPIs)

        self.editor.setLexer(self.lexer)
        self.editor.setCallTipsStyle(QsciScintilla.CallTipsContext)

        # self.editor.setCallTipsVisible(0)
        # Create an API for us to populate with our autocomplete terms
        self.api = QsciAPIs(self.lexer)

        # Compile the api for use in the lexer
        self.api.prepare()
Example #22
0
class ScriptTab(QWidget,):
    autocomplete_lst = []

    def __init__(self, parent, name, script="", filemodified=0):
        QWidget.__init__(self)
        self.parent = parent
        self.tabWidget = self.parent.ui.tabWidget
        self.gridlayout = QGridLayout(self)
        self._dirty = False

        self.initialize_editor()
        self.gridlayout.addWidget(self.editor)

        self.setAcceptDrops(True)

        self.filename = name
        self.filemodified = filemodified
        if self.is_file():
            self.title = os.path.basename(name)
            # if self.equals_saved():
            #    self.filemodified = os.path.getmtime(self.filename)
        else:
            self.filename = ""
            self.title = name

        # Show this file in the self.editor
        self.editor.setText(script)
        self.clean_txt = self.saved()

        self.update_dirty()

        self.editor.keyPressEvent = self.key_press_event

    @property
    def scriptRunner(self):
        return self.parent.controller.scriptRunner

    def wheelEvent(self, event):
        if event.modifiers() == Qt.ControlModifier:
            if event.delta() > 0:
                self.editor.zoomIn()
            else:
                self.editor.zoomOut()
        return QWidget.wheelEvent(self, event)


    def initialize_editor(self):



        self.editor = QsciScintilla()

#        self.editor.cursorPositionChanged.connect(self.e)
#        self.editor.copyAvailable.connect(self.e)
#        self.editor.indicatorClicked.connect(self.e)
#        self.editor.indicatorReleased.connect(self.e)
#        self.editor.linesChanged.connect(self.e)
#        self.editor.marginClicked.connect(self.e)
#        self.editor.modificationAttempted.connect(self.e)
#        self.editor.modificationChanged.connect(self.e)
#        self.editor.selectionChanged.connect(self.e)
#        self.editor.textChanged.connect(self.e)
#        self.editor.userListActivated.connect(self.e)

        if self.editor.__class__.__name__ == "LineTextWidget":
            return  # When using PySide without QSciScintilla

        # define the font to use
        font = QFont()
        font.setFamily("Consolas")
        font.setFixedPitch(True)
        font.setPointSize(10)
        # the font metrics here will help
        # building the margin width later
        fm = QFontMetrics(font)

        # set the default font of the self.editor
        # and take the same font for line numbers
        self.editor.setFont(font)
        self.editor.setMarginsFont(font)

        # Line numbers
        # conventionnaly, margin 0 is for line numbers
        self.editor.setMarginWidth(0, fm.width("00000") + 5)
        self.editor.setMarginLineNumbers(0, True)

        self.editor.setTabWidth(4)

        # Folding visual : we will use boxes
        self.editor.setFolding(QsciScintilla.BoxedTreeFoldStyle)

        self.editor.setAutoIndent(True)

        # Braces matching
        self.editor.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Editing line color
        self.editor.setCaretLineVisible(True)
        self.editor.setCaretLineBackgroundColor(QColor("#CDA869"))

        # Margins colors
        # line numbers margin
        self.editor.setMarginsBackgroundColor(QColor("#333333"))
        self.editor.setMarginsForegroundColor(QColor("#CCCCCC"))

        # folding margin colors (foreground,background)
        self.editor.setFoldMarginColors(QColor("#99CC66"), QColor("#333300"))

        # Choose a lexer
        self.lexer = QsciLexerPython()
        self.lexer.setDefaultFont(font)

        # Set the length of the string before the editor tries to autocomplete
        # In practise this would be higher than 1
        # But its set lower here to make the autocompletion more obvious
        self.editor.setAutoCompletionThreshold(1)
        # Tell the editor we are using a QsciAPI for the autocompletion
        self.editor.setAutoCompletionSource(QsciScintilla.AcsAPIs)

        self.editor.setLexer(self.lexer)
        self.editor.setCallTipsStyle(QsciScintilla.CallTipsContext)

        # self.editor.setCallTipsVisible(0)
        # Create an API for us to populate with our autocomplete terms
        self.api = QsciAPIs(self.lexer)

        # Compile the api for use in the lexer
        self.api.prepare()




#    def tefocusInEvent(self, event):
#        self.parent.focusInEvent(event)
#        return QTextEdit.focusInEvent(self.textEditScript, event)

    def is_file(self):
        return os.path.isfile(self.filename)

    def is_modified(self):
        if self.is_file():
            if self.equals_saved():
                self.filemodified = os.path.getmtime(self.filename)
            return str(os.path.getmtime(self.filename)) != str(self.filemodified)
        else:
            return False

    def saved(self):
        if self.is_file():
            f = open(self.filename)
            saved = f.read()
            f.close()
            return saved.strip()
        else:
            return ""

    def equals_saved(self):
        curr_lines = self.get_script().strip().splitlines()
        saved_lines = self.saved().strip().splitlines()
        if len(curr_lines) != len(saved_lines):
            return False
        for cl, sl in zip(curr_lines, saved_lines):
            if cl.strip() != sl.strip():
                return False
        return True


    @property
    def dirty(self):
        if not self._dirty:
            self.dirty = self.clean_txt != self.get_script()
        return self._dirty

    @dirty.setter
    def dirty(self, dirty):
        if dirty is False:
            self.clean_txt = self.get_script()
        if self._dirty != dirty:
            self._dirty = dirty
            self.filename_changed()

    def update_dirty(self):
        return self.dirty

    def index(self):
        return self.tabWidget.indexOf(self)

    def filename_changed(self):
        index = self.parent.ui.tabWidget.indexOf(self)
        if self.dirty:
            self.tabWidget.setTabText(index, "%s*" % self.title)
        else:
            self.tabWidget.setTabText(index, self.title)
 
    def reload(self):
        self.set_script(self.parent._load_script(self.filename))
        self.filemodified = os.path.getmtime(self.filename)
 
    def close(self):
        while self.dirty is True:  # While avoids data loss, in case save operation is aborted
            if self.filename == "":
                text = "Save unsaved changes?"
            else:
                text = "Save %s?" % self.filename
 
            ans = QMessageBox.question(self, 'Save', text, QMessageBox.Yes, QMessageBox.Cancel, QMessageBox.No)
 
            if ans == QMessageBox.Cancel:
                return
            elif ans == QMessageBox.Yes:
                self.parent.actionSave(False)
            elif ans == QMessageBox.No:
                break
        self.tabWidget.removeTab(self.index())
 
    def _save(self,):
        f = open(self.filename, 'w')
        f.write(self.get_script())
        f.close()
        self.title = os.path.basename(self.filename)
        self.dirty = False



    def key_press_event(self, event):

        self.update_dirty()
        if self.editor.__class__.__name__ == "LineTextWidget":
            return self.editor.edit.keyReleaseEvent(event)  # When using PySide without QSciScintilla

        QsciScintilla.keyPressEvent(self.editor, event)

        linenr, pos_in_line = self.editor.getCursorPosition()
        line = str(self.editor.text(linenr)[:pos_in_line])

        if event.key() == Qt.Key_F1:
            try:
                self.parent.show_documentation(getattr(self.scriptRunner, str(self.editor.selectedText())))
            except AttributeError:
                pass

        if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
            for tip in self.autocomplete_lst:
                try:
                    if line.endswith(tip[:tip.index('(') ]):
                        self.editor.insert(tip[tip.index('('):])
                        parent, residual = self.scriptRunner.split_line(line)
                        self.parent.show_documentation(self.scriptRunner.get_function_dict(parent, residual)[residual])
                        return
                except ValueError:
                    pass

        if event.key() < 100 or event.key() in [Qt.Key_Backspace, Qt.Key_Shift]:
            linenr, pos_in_line = self.editor.getCursorPosition()
            line = str(self.editor.text(linenr)[:pos_in_line])

            lst = self.scriptRunner.get_autocomplete_list(line)

            if lst is not None:
                self.api.clear()
                list(map(self.api.add, lst))
                self.api.prepare()
                if len(lst) > 0:
                    self.autocomplete_lst = lst

            self.editor.autoCompleteFromAll()
            shift_event = QKeyEvent(QEvent.KeyPress, Qt.Key_Shift, Qt.NoModifier)
            QsciScintilla.keyPressEvent(self.editor, shift_event)  # show autocomplete list




    def set_script(self, script):
        self.editor.setText(script)

    def get_script(self):
        return str(self.editor.text()).replace("\r", "").replace(">>> ", "").replace("\t", "    ")

    def dropHandler(self, dataItemList, ctrl, shift, event):
        pass
Example #23
0
 def _setup_editor(self):
     # set default editor settings
     self.editor.setFont(DEFAULT_FONT)
     self.editor.setMarginsFont(LINENO_FONT)
     self.editor.setBraceMatching(QsciScintilla.SloppyBraceMatch)
     self.editor.setCaretLineVisible(True)
     self.editor.setCaretForegroundColor(QColor('#000000'))
     self.editor.setCaretLineBackgroundColor(QColor('#c4e8fd'))
     self.editor.setAutoIndent(True)
     self.editor.setTabIndents(True)
     # tab width
     self.editor.setTabWidth(4)
     #  line numbers (margin 0)
     self.editor.setMarginLineNumbers(0, True)
     self.editor.setMarginWidth(0, '00000')
     # hide symbol margin
     self.editor.setMarginWidth(1, 0)
     # folding
     self.editor.setFolding(1)
     # indentation guides
     self.editor.setIndentationGuides(True)
     # wrap mode
     wrap_lines = False
     self.editor.setWrapMode(wrap_lines)
     if wrap_lines:
         # remove horizontal scrollBar
         self.editor.SendScintilla(SCI.SCI_SETHSCROLLBAR, 0, 0)
     lexer = QsciLexerPython(self)
     # apply default settings to lexer
     lexer.setDefaultFont(DEFAULT_FONT)
     lexer.setFont(DEFAULT_FONT)
     # margins
     lexer.setPaper(MARGIN_BGCOLOR, SCI.STYLE_LINENUMBER)
     lexer.setColor(MARGIN_COLOR, SCI.STYLE_LINENUMBER)
     lexer.setFont(DEFAULT_FONT, SCI.STYLE_LINENUMBER)
     # assign the lexer
     self.editor.setLexer(lexer)
     self.editor.SendScintilla(SCI.SCI_COLOURISE, 0, -1)
     # margins
     self.editor.setMarginsBackgroundColor(MARGIN_BGCOLOR)
     self.editor.setMarginsForegroundColor(MARGIN_COLOR)
     self.editor.setMarginsFont(LINENO_FONT)
     # folding
     self.editor.setFolding(FOLDING)
     self.editor.SendScintilla(SCI.SCI_SETMARGINWIDTHN, FOLD_MARGIN_NUM,
                               FOLD_MARGIN_WIDTH)
     # set fold margin colors
     self.editor.SendScintilla(SCI.SCI_SETFOLDMARGINCOLOUR, True,
                               Main._color_to_bgr_int(FOLD_MARGIN_COLOR))
     self.editor.SendScintilla(SCI.SCI_SETFOLDMARGINHICOLOUR, True,
                               Main._color_to_bgr_int(FOLD_MARGIN_HICOLOR))
     # create and configure the breakpoint column
     self.editor.setMarginWidth(self.__symbol_margin_num, 17)
     self.editor.markerDefine(BREAKPOINT_SYMBOL,
                              self.__breakpoint_marker_num)
     self.editor.setMarginMarkerMask(self.__symbol_margin_num,
                                     self.__breakpoint_marker_mask)
     self.editor.setMarkerBackgroundColor(BREAKPOINT_COLOR,
                                          self.__breakpoint_marker_num)
     # make breakpoint margin clickable
     self.editor.setMarginSensitivity(self.__symbol_margin_num, True)
     # add new callback for breakpoints
     self.editor.marginClicked.connect(self._slot_margin_clicked)
     # setup active line marker
     self.editor.markerDefine(ACTIVE_LINE_SYMBOL,
                              self.__active_line_marker_num)
     self.editor.setMarkerForegroundColor(ACTIVE_LINE_COLOR,
                                          self.__active_line_marker_num)
     self.editor.setMarkerBackgroundColor(ACTIVE_LINE_COLOR,
                                          self.__active_line_marker_num)
     # connect signals
     self.editor.textChanged.connect(self._slot_text_changed)
     self.editor.modificationChanged.connect(
         self._slot_editor_modification_changed)
     self.editor.SCN_URIDROPPED.connect(self._slot_file_dropped)
     self.editor.copyAvailable.connect(self.actionCut.setEnabled)
     self.editor.copyAvailable.connect(self.actionCopy.setEnabled)
     self.editor.selectionChanged.connect(
         lambda: self.actionDelete.setEnabled(self.editor.hasSelectedText()
                                              ))
     self.editor.selectionChanged.connect(
         lambda: self.actionSelectAll.setEnabled(self.editor.
                                                 hasSelectedText()))
     # autocomplete
     if API_FILE is not None:
         apis = QsciAPIs(self.editor.lexer())
         apis.loadPrepared(API_FILE)
     self.editor.setAutoCompletionThreshold(3)
     # The source is any installed APIs.
     self.editor.setAutoCompletionSource(QsciScintilla.AcsAPIs)
Example #24
0
    def __init__(self, parent=None):
        #CONSTRUCTOR DE LA CLASE HEREDADA
        super(editor, self).__init__(parent)

        #DEFINICION DE LA FUENTE Y SUS PROPIEDADES
        font = QFont()
        #font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.setMarginsFont(font)
        self.lineas_marcadas = []

        #PROFIEDADES AVANZADAS DE LA FUENTE DEL EDITOR
        fontmetrics = QFontMetrics(font)
        self.setMarginsFont(font)
        #SE CAMBIA EL MARGEN ANCHO DE LA FUNETE
        self.setMarginWidth(0, fontmetrics.width("00000") - 15)
        #MARGEN DE LOS NUMEROS DE LINEA
        self.setMarginLineNumbers(0, True)
        #COLOR DE FONDO DE LOS NUMEROS DE LINEA
        self.setMarginsBackgroundColor(QColor("#E0E0E0"))

        self.setMarginSensitivity(1, True)

        #CREAMOS LA SEÑA PARA AGREGAR LINEA MARCADA
        self.marginClicked.connect(self.on_margin_clicked)

        #SE DEFINE EL ICONO A MOSTRAR EN LA LINEA MARCADA
        self.markerDefine(QsciScintilla.Circle, self.ARROW_MARKER_NUM)
        self.setMarkerBackgroundColor(QColor("#FF6C3B"), self.ARROW_MARKER_NUM)

        #RESALTADO DE PARENTECIS,CORCHETES Y OTROS
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        #RESALTADO DE LA LINEA DONDE SE ENCUENTRA EL CURSOR
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QColor("#32f24c"))

        #AUTOIDENTACION
        self.setAutoIndent(True)
        self.setIndentationGuides(True)
        self.setIndentationsUseTabs(True)
        self.setIndentationWidth(4)

        #DEFINIMOS EL RESALTADO O LEXER
        self.lexer = QsciLexerPascal()
        self.lexer.setDefaultFont(font)  #FUENTE DEL LEXER

        #API PARA EL AUTOCOMPETADO
        api = QsciAPIs(self.lexer)
        self.palabraAutocompletar(api)
        api.prepare()

        self.cambiarColores()
        self.setLexer(self.lexer)
        self.setAutoCompletionThreshold(1)
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)

        #ESCONDER SCROLLBAR HORIZONTAL
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
        #TAMAÑO MINIMO DEL EDITOR
        self.setMinimumSize(600, 360)
Example #25
0
class EditorPane(QsciScintilla):
    """
    Represents the text editor.
    """

    # Signal fired when a script or hex is droped on this editor
    open_file = pyqtSignal(str)

    def __init__(self, path, text, newline=NEWLINE):
        super().__init__()
        self.setUtf8(True)
        self.path = path
        self.setText(text)
        self.newline = newline
        self.check_indicators = {  # IDs are arbitrary
            'error': {'id': 19, 'markers': {}},
            'style': {'id': 20, 'markers': {}}
        }
        self.BREAKPOINT_MARKER = 23  # Arbitrary
        self.search_indicators = {
            'selection': {'id': 21, 'positions': []}
        }
        self.previous_selection = {
            'line_start': 0, 'col_start': 0, 'line_end': 0, 'col_end': 0
        }
        self.lexer = PythonLexer()
        self.api = None
        self.has_annotations = False
        self.setModified(False)
        self.breakpoint_lines = set()
        self.configure()

    def dropEvent(self, event):
        """
        Run by Qt when *something* is dropped on this editor
        """

        # Does the drag event have any urls?
        # Files are transfered as a url (by path not value)
        if event.mimeData().hasUrls():
            # Qt doesn't seem to have an 'open' action,
            # this seems the most appropriate
            event.setDropAction(Qt.CopyAction)
            # Valid links
            links = []
            # Iterate over each of the urls attached to the event
            for url in event.mimeData().urls():
                # Check the url is to a local file
                # (not a webpage for example)
                if url.isLocalFile():
                    # Grab a 'real' path from the url
                    path = url.toLocalFile()
                    # Add it to the list of valid links
                    links.append(path)

            # Did we get any?
            if len(links) > 0:
                # Only accept now we actually know we can do
                # something with the drop event
                event.accept()
                for link in links:
                    # Start bubbling an open file request
                    self.open_file.emit(link)

        # If the event wasn't handled let QsciScintilla have a go
        if not event.isAccepted():
            super().dropEvent(event)

    def configure(self):
        """
        Set up the editor component.
        """
        # Font information
        font = Font().load()
        self.setFont(font)
        # Generic editor settings
        self.setUtf8(True)
        self.setAutoIndent(True)
        self.setIndentationsUseTabs(False)
        self.setIndentationWidth(4)
        self.setIndentationGuides(True)
        self.setBackspaceUnindents(True)
        self.setTabWidth(4)
        self.setEdgeColumn(79)
        self.setMarginLineNumbers(0, True)
        self.setMarginWidth(0, 50)
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
        self.set_theme()
        # Markers and indicators
        self.setMarginSensitivity(0, True)
        self.markerDefine(self.Circle, self.BREAKPOINT_MARKER)
        self.setMarginSensitivity(1, True)
        self.setIndicatorDrawUnder(True)
        for type_ in self.check_indicators:
            self.indicatorDefine(
                self.SquiggleIndicator, self.check_indicators[type_]['id'])
        for type_ in self.search_indicators:
            self.indicatorDefine(
                self.StraightBoxIndicator, self.search_indicators[type_]['id'])
        self.setAnnotationDisplay(self.AnnotationBoxed)
        self.selectionChanged.connect(self.selection_change_listener)

    def connect_margin(self, func):
        """
        Connect clicking the margin to the passed in handler function.
        """
        self.marginClicked.connect(func)

    def set_theme(self, theme=DayTheme):
        """
        Connect the theme to a lexer and return the lexer for the editor to
        apply to the script text.
        """
        theme.apply_to(self.lexer)
        self.lexer.setDefaultPaper(theme.Paper)
        self.setCaretForegroundColor(theme.Caret)
        self.setMarginsBackgroundColor(theme.Margin)
        self.setMarginsForegroundColor(theme.Caret)
        self.setIndicatorForegroundColor(theme.IndicatorError,
                                         self.check_indicators['error']['id'])
        self.setIndicatorForegroundColor(theme.IndicatorStyle,
                                         self.check_indicators['style']['id'])
        for type_ in self.search_indicators:
            self.setIndicatorForegroundColor(
                theme.IndicatorWordMatch, self.search_indicators[type_]['id'])
        self.setMarkerBackgroundColor(theme.BreakpointMarker,
                                      self.BREAKPOINT_MARKER)
        self.setAutoCompletionThreshold(2)
        self.setAutoCompletionSource(QsciScintilla.AcsAll)
        self.setLexer(self.lexer)
        self.setMatchedBraceBackgroundColor(theme.BraceBackground)
        self.setMatchedBraceForegroundColor(theme.BraceForeground)
        self.setUnmatchedBraceBackgroundColor(theme.UnmatchedBraceBackground)
        self.setUnmatchedBraceForegroundColor(theme.UnmatchedBraceForeground)

    def set_api(self, api_definitions):
        """
        Sets the API entries for tooltips, calltips and the like.
        """
        self.api = QsciAPIs(self.lexer)
        for entry in api_definitions:
            self.api.add(entry)
        self.api.prepare()

    @property
    def label(self):
        """
        The label associated with this editor widget (usually the filename of
        the script we're editing).

        If the script has been modified since it was last saved, the label will
        end with an asterisk.
        """
        if self.path:
            label = os.path.basename(self.path)
        else:
            label = 'untitled'
        # Add an asterisk to indicate that the file remains unsaved.
        if self.isModified():
            return label + ' *'
        else:
            return label

    def reset_annotations(self):
        """
        Clears all the assets (indicators, annotations and markers).
        """
        self.clearAnnotations()
        self.markerDeleteAll()
        self.reset_search_indicators()
        self.reset_check_indicators()

    def reset_check_indicators(self):
        """
        Clears all the text indicators related to the check code functionality.
        """
        for indicator in self.check_indicators:
            for _, markers in \
                    self.check_indicators[indicator]['markers'].items():
                line_no = markers[0]['line_no']  # All markers on same line.
                self.clearIndicatorRange(
                    line_no, 0, line_no, 999999,
                    self.check_indicators[indicator]['id'])
            self.check_indicators[indicator]['markers'] = {}

    def reset_search_indicators(self):
        """
        Clears all the text indicators from the search functionality.
        """
        for indicator in self.search_indicators:
            for position in self.search_indicators[indicator]['positions']:
                self.clearIndicatorRange(
                    position['line_start'], position['col_start'],
                    position['line_end'], position['col_end'],
                    self.search_indicators[indicator]['id'])
            self.search_indicators[indicator]['positions'] = []

    def annotate_code(self, feedback, annotation_type='error'):
        """
        Given a list of annotations add them to the editor pane so the user can
        act upon them.
        """
        indicator = self.check_indicators[annotation_type]
        for line_no, messages in feedback.items():
            indicator['markers'][line_no] = messages
            for message in messages:
                col = message.get('column', 0)
                if col:
                    col_start = col - 1
                    col_end = col + 1
                    self.fillIndicatorRange(line_no, col_start, line_no,
                                            col_end, indicator['id'])

    def show_annotations(self):
        """
        Display all the messages to be annotated to the code.
        """
        lines = defaultdict(list)
        for indicator in self.check_indicators:
            markers = self.check_indicators[indicator]['markers']
            for k, marker_list in markers.items():
                for m in marker_list:
                    lines[m['line_no']].append('\u2191' +
                                               m['message'].capitalize())
        for line, messages in lines.items():
            text = '\n'.join(messages).strip()
            if text:
                self.annotate(line, text, self.annotationDisplay())

    def find_next_match(self, text, from_line=-1, from_col=-1,
                        case_sensitive=True, wrap_around=True):
        """
        Finds the next text match from the current cursor, or the given
        position, and selects it (the automatic selection is the only available
        QsciScintilla behaviour).
        Returns True if match found, False otherwise.
        """
        return self.findFirst(
            text,            # Text to find,
            False,           # Treat as regular expression
            case_sensitive,  # Case sensitive search
            True,            # Whole word matches only
            wrap_around,     # Wrap search
            forward=True,    # Forward search
            line=from_line,  # -1 starts at current position
            index=from_col,  # -1 starts at current position
            show=False,      # Unfolds found text
            posix=False)     # More POSIX compatible RegEx

    def range_from_positions(self, start_position, end_position):
        """Given a start-end pair, such as are provided by a regex match,
        return the corresponding Scintilla line-offset pairs which are
        used for searches, indicators etc.

        FIXME: Not clear whether the Scintilla conversions are expecting
        bytes or characters (ie codepoints)
        """
        start_line, start_offset = self.lineIndexFromPosition(start_position)
        end_line, end_offset = self.lineIndexFromPosition(end_position)
        return start_line, start_offset, end_line, end_offset

    def highlight_selected_matches(self):
        """
        Checks the current selection, if it is a single word it then searches
        and highlights all matches.

        Since we're interested in exactly one word:
        * Ignore an empty selection
        * Ignore anything which spans more than one line
        * Ignore more than one word
        * Ignore anything less than one word
        """
        selected_range = line0, col0, line1, col1 = self.getSelection()
        #
        # If there's no selection, do nothing
        #
        if selected_range == (-1, -1, -1, -1):
            return

        #
        # Ignore anything which spans two or more lines
        #
        if line0 != line1:
            return

        #
        # Ignore if no text is selected or the selected text is not at most one
        # valid identifier-type word.
        #
        selected_text = self.selectedText()
        if not RE_VALID_WORD.match(selected_text):
            return

        #
        # Ignore anything which is not a whole word.
        # NB Although Scintilla defines a SCI_ISRANGEWORD message,
        # it's not exposed by QSciScintilla. Instead, we
        # ask Scintilla for the start end end position of
        # the word we're in and test whether our range end points match
        # those or not.
        #
        pos0 = self.positionFromLineIndex(line0, col0)
        word_start_pos = self.SendScintilla(
            QsciScintilla.SCI_WORDSTARTPOSITION, pos0, 1)
        _, start_offset = self.lineIndexFromPosition(word_start_pos)
        if col0 != start_offset:
            return

        pos1 = self.positionFromLineIndex(line1, col1)
        word_end_pos = self.SendScintilla(
            QsciScintilla.SCI_WORDENDPOSITION, pos1, 1)
        _, end_offset = self.lineIndexFromPosition(word_end_pos)
        if col1 != end_offset:
            return

        #
        # For each matching word within the editor text, add it to
        # the list of highlighted indicators and fill it according
        # to the current theme.
        #
        indicators = self.search_indicators['selection']
        text = self.text()
        for match in re.finditer(selected_text, text):
            range = self.range_from_positions(*match.span())
            #
            # Don't highlight the text we've selected
            #
            if range == selected_range:
                continue

            line_start, col_start, line_end, col_end = range
            indicators['positions'].append({
                'line_start': line_start, 'col_start': col_start,
                'line_end': line_end, 'col_end': col_end
            })
            self.fillIndicatorRange(line_start, col_start, line_end,
                                    col_end, indicators['id'])

    def selection_change_listener(self):
        """
        Runs every time the text selection changes. This could get triggered
        multiple times while the mouse click is down, even if selection has not
        changed in itself.
        If there is a new selection is passes control to
        highlight_selected_matches.
        """
        # Get the current selection, exit if it has not changed
        line_from, index_from, line_to, index_to = self.getSelection()
        if self.previous_selection['col_end'] != index_to or \
                self.previous_selection['col_start'] != index_from or \
                self.previous_selection['line_start'] != line_from or \
                self.previous_selection['line_end'] != line_to:
            self.previous_selection['line_start'] = line_from
            self.previous_selection['col_start'] = index_from
            self.previous_selection['line_end'] = line_to
            self.previous_selection['col_end'] = index_to
            # Highlight matches
            self.reset_search_indicators()
            self.highlight_selected_matches()
Example #26
0
File: editor.py Project: zencuke/mu
class EditorPane(QsciScintilla):
    """
    Represents the text editor.
    """

    # Signal fired when a script or hex is droped on this editor
    open_file = pyqtSignal(str)

    def __init__(self, path, text, newline=NEWLINE):
        super().__init__()
        self.setUtf8(True)
        self.path = path
        self.setText(text)
        self.newline = newline
        self.check_indicators = {  # IDs are arbitrary
            'error': {
                'id': 19,
                'markers': {}
            },
            'style': {
                'id': 20,
                'markers': {}
            }
        }
        self.search_indicators = {'selection': {'id': 21, 'positions': []}}
        self.DEBUG_INDICATOR = 22  # Arbitrary
        self.BREAKPOINT_MARKER = 23  # Arbitrary
        self.previous_selection = {
            'line_start': 0,
            'col_start': 0,
            'line_end': 0,
            'col_end': 0
        }
        self.lexer = PythonLexer()
        self.api = None
        self.has_annotations = False
        self.setModified(False)
        self.breakpoint_handles = set()
        self.configure()

    def dropEvent(self, event):
        """
        Run by Qt when *something* is dropped on this editor
        """

        # Does the drag event have any urls?
        # Files are transfered as a url (by path not value)
        if event.mimeData().hasUrls():
            # Qt doesn't seem to have an 'open' action,
            # this seems the most appropriate
            event.setDropAction(Qt.CopyAction)
            # Valid links
            links = []
            # Iterate over each of the urls attached to the event
            for url in event.mimeData().urls():
                # Check the url is to a local file
                # (not a webpage for example)
                if url.isLocalFile():
                    # Grab a 'real' path from the url
                    path = url.toLocalFile()
                    # Add it to the list of valid links
                    links.append(path)

            # Did we get any?
            if len(links) > 0:
                # Only accept now we actually know we can do
                # something with the drop event
                event.accept()
                for link in links:
                    # Start bubbling an open file request
                    self.open_file.emit(link)

        # If the event wasn't handled let QsciScintilla have a go
        if not event.isAccepted():
            super().dropEvent(event)

    def configure(self):
        """
        Set up the editor component.
        """
        # Font information
        font = Font().load()
        self.setFont(font)
        # Generic editor settings
        self.setUtf8(True)
        self.setAutoIndent(True)
        self.setIndentationsUseTabs(False)
        self.setIndentationWidth(4)
        self.setIndentationGuides(True)
        self.setBackspaceUnindents(True)
        self.setTabWidth(4)
        self.setEdgeColumn(79)
        self.setMarginLineNumbers(0, True)
        self.setMarginWidth(0, 50)
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
        self.set_theme()
        # Markers and indicators
        self.setMarginSensitivity(0, True)
        self.markerDefine(self.Circle, self.BREAKPOINT_MARKER)
        self.setMarginSensitivity(1, True)
        self.setIndicatorDrawUnder(True)
        for type_ in self.check_indicators:
            self.indicatorDefine(self.SquiggleIndicator,
                                 self.check_indicators[type_]['id'])
        for type_ in self.search_indicators:
            self.indicatorDefine(self.StraightBoxIndicator,
                                 self.search_indicators[type_]['id'])
        self.indicatorDefine(self.FullBoxIndicator, self.DEBUG_INDICATOR)
        self.setAnnotationDisplay(self.AnnotationBoxed)
        self.selectionChanged.connect(self.selection_change_listener)
        self.set_zoom()

    def connect_margin(self, func):
        """
        Connect clicking the margin to the passed in handler function.
        """
        self.marginClicked.connect(func)

    def set_theme(self, theme=DayTheme):
        """
        Connect the theme to a lexer and return the lexer for the editor to
        apply to the script text.
        """
        theme.apply_to(self.lexer)
        self.lexer.setDefaultPaper(theme.Paper)
        self.setCaretForegroundColor(theme.Caret)
        self.setIndicatorForegroundColor(theme.IndicatorError,
                                         self.check_indicators['error']['id'])
        self.setIndicatorForegroundColor(theme.IndicatorStyle,
                                         self.check_indicators['style']['id'])
        self.setIndicatorForegroundColor(theme.DebugStyle,
                                         self.DEBUG_INDICATOR)
        for type_ in self.search_indicators:
            self.setIndicatorForegroundColor(
                theme.IndicatorWordMatch, self.search_indicators[type_]['id'])
        self.setMarkerBackgroundColor(theme.BreakpointMarker,
                                      self.BREAKPOINT_MARKER)
        self.setAutoCompletionThreshold(2)
        self.setAutoCompletionSource(QsciScintilla.AcsAll)
        self.setLexer(self.lexer)
        self.setMarginsBackgroundColor(theme.Margin)
        self.setMarginsForegroundColor(theme.Caret)
        self.setMatchedBraceBackgroundColor(theme.BraceBackground)
        self.setMatchedBraceForegroundColor(theme.BraceForeground)
        self.setUnmatchedBraceBackgroundColor(theme.UnmatchedBraceBackground)
        self.setUnmatchedBraceForegroundColor(theme.UnmatchedBraceForeground)

    def set_api(self, api_definitions):
        """
        Sets the API entries for tooltips, calltips and the like.
        """
        self.api = QsciAPIs(self.lexer)
        for entry in api_definitions:
            self.api.add(entry)
        self.api.prepare()

    def set_zoom(self, size='m'):
        """
        Sets the font zoom to the specified base point size for all fonts given
        a t-shirt size.
        """
        sizes = {
            'xs': -4,
            's': -2,
            'm': 1,
            'l': 4,
            'xl': 8,
            'xxl': 16,
            'xxxl': 48,
        }
        self.zoomTo(sizes[size])

    @property
    def label(self):
        """
        The label associated with this editor widget (usually the filename of
        the script we're editing).

        If the script has been modified since it was last saved, the label will
        end with an asterisk.
        """
        if self.path:
            label = os.path.basename(self.path)
        else:
            label = _('untitled')
        # Add an asterisk to indicate that the file remains unsaved.
        if self.isModified():
            return label + ' *'
        else:
            return label

    def reset_annotations(self):
        """
        Clears all the assets (indicators, annotations and markers).
        """
        self.clearAnnotations()
        self.markerDeleteAll()
        self.reset_search_indicators()
        self.reset_check_indicators()

    def reset_check_indicators(self):
        """
        Clears all the text indicators related to the check code functionality.
        """
        for indicator in self.check_indicators:
            for _, markers in \
                    self.check_indicators[indicator]['markers'].items():
                line_no = markers[0]['line_no']  # All markers on same line.
                self.clearIndicatorRange(
                    line_no, 0, line_no, 999999,
                    self.check_indicators[indicator]['id'])
            self.check_indicators[indicator]['markers'] = {}

    def reset_search_indicators(self):
        """
        Clears all the text indicators from the search functionality.
        """
        for indicator in self.search_indicators:
            for position in self.search_indicators[indicator]['positions']:
                self.clearIndicatorRange(
                    position['line_start'], position['col_start'],
                    position['line_end'], position['col_end'],
                    self.search_indicators[indicator]['id'])
            self.search_indicators[indicator]['positions'] = []

    def annotate_code(self, feedback, annotation_type='error'):
        """
        Given a list of annotations add them to the editor pane so the user can
        act upon them.
        """
        indicator = self.check_indicators[annotation_type]
        for line_no, messages in feedback.items():
            indicator['markers'][line_no] = messages
            for message in messages:
                col = message.get('column', 0)
                if col:
                    col_start = col - 1
                    col_end = col + 1
                    self.fillIndicatorRange(line_no, col_start, line_no,
                                            col_end, indicator['id'])
        if feedback:
            # Ensure the first line with a problem is visible.
            first_problem_line = sorted(feedback.keys())[0]
            self.ensureLineVisible(first_problem_line)

    def debugger_at_line(self, line):
        """
        Set the line to be highlighted with the DEBUG_INDICATOR.
        """
        self.reset_debugger_highlight()
        # Calculate the line length & account for \r\n giving ObOE.
        line_length = len(self.text(line).rstrip())
        self.fillIndicatorRange(line, 0, line, line_length,
                                self.DEBUG_INDICATOR)
        self.ensureLineVisible(line)

    def reset_debugger_highlight(self):
        """
        Reset all the lines so the DEBUG_INDICATOR is no longer displayed.

        We need to check each line since there's no way to tell what the
        currently highlighted line is. This approach also has the advantage of
        resetting the *whole* editor pane.
        """
        for i in range(self.lines()):
            line_length = len(self.text(i))
            self.clearIndicatorRange(i, 0, i, line_length,
                                     self.DEBUG_INDICATOR)

    def show_annotations(self):
        """
        Display all the messages to be annotated to the code.
        """
        lines = defaultdict(list)
        for indicator in self.check_indicators:
            markers = self.check_indicators[indicator]['markers']
            for k, marker_list in markers.items():
                for m in marker_list:
                    lines[m['line_no']].append('\u2191 ' + m['message'])
        for line, messages in lines.items():
            text = '\n'.join(messages).strip()
            if text:
                self.annotate(line, text, self.annotationDisplay())

    def find_next_match(self,
                        text,
                        from_line=-1,
                        from_col=-1,
                        case_sensitive=True,
                        wrap_around=True):
        """
        Finds the next text match from the current cursor, or the given
        position, and selects it (the automatic selection is the only available
        QsciScintilla behaviour).
        Returns True if match found, False otherwise.
        """
        return self.findFirst(
            text,  # Text to find,
            False,  # Treat as regular expression
            case_sensitive,  # Case sensitive search
            True,  # Whole word matches only
            wrap_around,  # Wrap search
            forward=True,  # Forward search
            line=from_line,  # -1 starts at current position
            index=from_col,  # -1 starts at current position
            show=False,  # Unfolds found text
            posix=False)  # More POSIX compatible RegEx

    def range_from_positions(self, start_position, end_position):
        """Given a start-end pair, such as are provided by a regex match,
        return the corresponding Scintilla line-offset pairs which are
        used for searches, indicators etc.

        NOTE: Arguments must be byte offsets into the underlying text bytes.
        """
        start_line, start_offset = self.lineIndexFromPosition(start_position)
        end_line, end_offset = self.lineIndexFromPosition(end_position)
        return start_line, start_offset, end_line, end_offset

    def highlight_selected_matches(self):
        """
        Checks the current selection, if it is a single word it then searches
        and highlights all matches.

        Since we're interested in exactly one word:
        * Ignore an empty selection
        * Ignore anything which spans more than one line
        * Ignore more than one word
        * Ignore anything less than one word
        """
        selected_range = line0, col0, line1, col1 = self.getSelection()
        #
        # If there's no selection, do nothing
        #
        if selected_range == (-1, -1, -1, -1):
            return

        #
        # Ignore anything which spans two or more lines
        #
        if line0 != line1:
            return

        #
        # Ignore if no text is selected or the selected text is not at most one
        # valid identifier-type word.
        #
        selected_text = self.selectedText()
        if not RE_VALID_WORD.match(selected_text):
            return

        #
        # Ignore anything which is not a whole word.
        # NB Although Scintilla defines a SCI_ISRANGEWORD message,
        # it's not exposed by QSciScintilla. Instead, we
        # ask Scintilla for the start end end position of
        # the word we're in and test whether our range end points match
        # those or not.
        #
        pos0 = self.positionFromLineIndex(line0, col0)
        word_start_pos = self.SendScintilla(
            QsciScintilla.SCI_WORDSTARTPOSITION, pos0, 1)
        _, start_offset = self.lineIndexFromPosition(word_start_pos)
        if col0 != start_offset:
            return

        pos1 = self.positionFromLineIndex(line1, col1)
        word_end_pos = self.SendScintilla(QsciScintilla.SCI_WORDENDPOSITION,
                                          pos1, 1)
        _, end_offset = self.lineIndexFromPosition(word_end_pos)
        if col1 != end_offset:
            return

        #
        # For each matching word within the editor text, add it to
        # the list of highlighted indicators and fill it according
        # to the current theme.
        #
        indicators = self.search_indicators['selection']
        encoding = 'utf8' if self.isUtf8() else 'latin1'
        text_bytes = self.text().encode(encoding)
        selected_text_bytes = selected_text.encode(encoding)
        for match in re.finditer(selected_text_bytes, text_bytes):
            range = self.range_from_positions(*match.span())
            #
            # Don't highlight the text we've selected
            #
            if range == selected_range:
                continue

            line_start, col_start, line_end, col_end = range
            indicators['positions'].append({
                'line_start': line_start,
                'col_start': col_start,
                'line_end': line_end,
                'col_end': col_end
            })
            self.fillIndicatorRange(line_start, col_start, line_end, col_end,
                                    indicators['id'])

    def selection_change_listener(self):
        """
        Runs every time the text selection changes. This could get triggered
        multiple times while the mouse click is down, even if selection has not
        changed in itself.
        If there is a new selection is passes control to
        highlight_selected_matches.
        """
        # Get the current selection, exit if it has not changed
        line_from, index_from, line_to, index_to = self.getSelection()
        if self.previous_selection['col_end'] != index_to or \
                self.previous_selection['col_start'] != index_from or \
                self.previous_selection['line_start'] != line_from or \
                self.previous_selection['line_end'] != line_to:
            self.previous_selection['line_start'] = line_from
            self.previous_selection['col_start'] = index_from
            self.previous_selection['line_end'] = line_to
            self.previous_selection['col_end'] = index_to
            # Highlight matches
            self.reset_search_indicators()
            self.highlight_selected_matches()

    def toggle_line(self, raw_line):
        """
        Given a raw_line, will return the toggled version of it.
        """
        clean_line = raw_line.strip()
        if clean_line.startswith('#'):
            # It's a comment line, so handle "# " & "#..." as fallback:
            if clean_line.startswith('# '):
                return raw_line.replace('# ', '')
            else:
                return raw_line.replace('#', '')
        elif clean_line:
            # It's a normal line of code.
            return '# ' + raw_line
        else:
            # It's a whitespace line, so just return it.
            return raw_line

    def toggle_comments(self):
        """
        Iterate through the selected lines and toggle their comment/uncomment
        state. So, lines that are not comments become comments and vice versa.
        """
        if self.hasSelectedText():
            # Toggle currently selected text.
            logger.info("Toggling comments")
            line_from, index_from, line_to, index_to = self.getSelection()
            selected_text = self.selectedText()
            lines = selected_text.split('\n')
            toggled_lines = []
            for line in lines:
                toggled_lines.append(self.toggle_line(line))
            new_text = '\n'.join(toggled_lines)
            self.replaceSelectedText(new_text)
            # Ensure the new text is also selected.
            last_newline = toggled_lines[-1]
            last_oldline = lines[-1].strip()
            if last_newline.startswith('#'):
                index_to += 2  # A newly commented line starts... "# "
            elif last_oldline.startswith('#'):
                # Check the original line to see what has been uncommented.
                if last_oldline.startswith('# '):
                    index_to -= 2  # It was "# ".
                else:
                    index_to -= 1  # It was "#".
            self.setSelection(line_from, index_from, line_to, index_to)
        else:
            # Toggle the line currently containing the cursor.
            line_number, column = self.getCursorPosition()
            logger.info('Toggling line {}'.format(line_number))
            line_content = self.text(line_number)
            new_line = self.toggle_line(line_content)
            self.setSelection(line_number, 0, line_number, len(line_content))
            self.replaceSelectedText(new_line)
            self.setSelection(line_number, 0, line_number, len(new_line) - 1)
Example #27
0
    def __init__(self, parent=None, lineNumberOn=False):
        super(highlightEditor, self).__init__()

        self.editor = QsciScintilla(parent)
        font = QFont()
        font.setFamily("Consolas")
        font.setPointSize(12)
        font.setFixedPitch(True)
        self.editor.setFont(font)
        self.editor.setObjectName("editor")

        self.editor.setUtf8(True)
        self.editor.setMarginsFont(font)
        if lineNumberOn:
            self.editor.setMarginWidth(
                0,
                len(str(len(self.editor.text().split('\n')))) * 20)
        self.editor.setMarginLineNumbers(0, lineNumberOn)

        self.editor.setBraceMatching(QsciScintilla.StrictBraceMatch)

        self.editor.setIndentationsUseTabs(True)
        self.editor.setIndentationWidth(4)
        self.editor.setTabIndents(True)
        self.editor.setAutoIndent(True)
        self.editor.setBackspaceUnindents(True)
        self.editor.setTabWidth(4)

        self.editor.setCaretLineVisible(True)
        self.editor.setCaretLineBackgroundColor(QColor('#DCDCDC'))

        self.editor.setIndentationGuides(True)

        self.editor.setFolding(QsciScintilla.PlainFoldStyle)
        self.editor.setMarginWidth(2, 12)

        self.editor.markerDefine(QsciScintilla.Minus,
                                 QsciScintilla.SC_MARKNUM_FOLDEROPEN)
        self.editor.markerDefine(QsciScintilla.Plus,
                                 QsciScintilla.SC_MARKNUM_FOLDER)
        self.editor.markerDefine(QsciScintilla.Minus,
                                 QsciScintilla.SC_MARKNUM_FOLDEROPENMID)
        self.editor.markerDefine(QsciScintilla.Plus,
                                 QsciScintilla.SC_MARKNUM_FOLDEREND)

        self.editor.setMarkerBackgroundColor(
            QColor("#FFFFFF"), QsciScintilla.SC_MARKNUM_FOLDEREND)
        self.editor.setMarkerForegroundColor(
            QColor("#272727"), QsciScintilla.SC_MARKNUM_FOLDEREND)
        self.editor.setMarkerBackgroundColor(
            QColor("#FFFFFF"), QsciScintilla.SC_MARKNUM_FOLDEROPENMID)
        self.editor.setMarkerForegroundColor(
            QColor("#272727"), QsciScintilla.SC_MARKNUM_FOLDEROPENMID)
        self.editor.setAutoCompletionSource(QsciScintilla.AcsAll)
        self.editor.setAutoCompletionCaseSensitivity(True)
        self.editor.setAutoCompletionReplaceWord(False)
        self.editor.setAutoCompletionThreshold(1)
        self.editor.setAutoCompletionUseSingle(QsciScintilla.AcusExplicit)
        self.lexer = highlight(self.editor)
        self.editor.setLexer(self.lexer)
        self.__api = QsciAPIs(self.lexer)
        autocompletions = keyword.kwlist + []
        for ac in autocompletions:
            self.__api.add(ac)
        self.__api.prepare()
        self.editor.autoCompleteFromAll()

        self.editor.textChanged.connect(self.changed)
Example #28
0
class EditorPane(QsciScintilla):
    """
    Represents the text editor.
    """

    # Signal fired when a script or hex is droped on this editor.
    open_file = pyqtSignal(str)
    # Signal fired when a context menu is requested.
    context_menu = pyqtSignal()

    def __init__(self, path, text, newline=NEWLINE):
        super().__init__()
        self.setUtf8(True)
        self.path = path
        self.setText(text)
        self.newline = newline
        self.check_indicators = {  # IDs are arbitrary
            "error": {"id": 19, "markers": {}},
            "style": {"id": 20, "markers": {}},
        }
        self.search_indicators = {"selection": {"id": 21, "positions": []}}
        self.DEBUG_INDICATOR = 22  # Arbitrary
        self.BREAKPOINT_MARKER = 23  # Arbitrary
        self.previous_selection = {
            "line_start": 0,
            "col_start": 0,
            "line_end": 0,
            "col_end": 0,
        }
        if self.path:
            if self.path.endswith(".css"):
                self.lexer = CssLexer()
            elif self.path.endswith(".html") or self.path.endswith(".htm"):
                self.lexer = QsciLexerHTML()
                self.lexer.setDjangoTemplates(True)
            else:
                self.lexer = PythonLexer()
        else:
            self.lexer = PythonLexer()
        self.api = None
        self.has_annotations = False
        self.setModified(False)
        self.breakpoint_handles = set()
        self.configure()

    def contextMenuEvent(self, event):
        """
        A context menu (right click) has been actioned.
        """
        self.context_menu.emit()

    def wheelEvent(self, event):
        """
        Stops QScintilla from doing the wrong sort of zoom handling.
        """
        if not QApplication.keyboardModifiers():
            super().wheelEvent(event)

    def dropEvent(self, event):
        """
        Run by Qt when *something* is dropped on this editor
        """

        # Does the drag event have any urls?
        # Files are transfered as a url (by path not value)
        if event.mimeData().hasUrls():
            # Qt doesn't seem to have an 'open' action,
            # this seems the most appropriate
            event.setDropAction(Qt.CopyAction)
            # Valid links
            links = []
            # Iterate over each of the urls attached to the event
            for url in event.mimeData().urls():
                # Check the url is to a local file
                # (not a webpage for example)
                if url.isLocalFile():
                    # Grab a 'real' path from the url
                    path = url.toLocalFile()
                    # Add it to the list of valid links
                    links.append(path)

            # Did we get any?
            if len(links) > 0:
                # Only accept now we actually know we can do
                # something with the drop event
                event.accept()
                for link in links:
                    # Start bubbling an open file request
                    self.open_file.emit(link)

        # If the event wasn't handled let QsciScintilla have a go
        if not event.isAccepted():
            super().dropEvent(event)

    def configure(self):
        """
        Set up the editor component.
        """
        # Font information
        font = Font().load()
        self.setFont(font)
        # Generic editor settings
        self.setUtf8(True)
        self.setAutoIndent(True)
        self.setIndentationsUseTabs(False)
        self.setIndentationWidth(4)
        self.setIndentationGuides(True)
        self.setBackspaceUnindents(True)
        self.setTabWidth(4)
        self.setEdgeColumn(79)
        self.setMarginLineNumbers(0, True)
        self.setMarginWidth(0, 50)
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
        self.set_theme()
        # Markers and indicators
        self.setMarginSensitivity(0, True)
        self.markerDefine(self.Circle, self.BREAKPOINT_MARKER)
        self.setMarginSensitivity(1, True)
        # Additional dummy margin to prevent accidental breakpoint toggles when
        # trying to position the edit cursor to the left of the first column,
        # using the mouse and not being 100% accurate. This margin needs to be
        # set with "sensitivity on": otherwise clicking it would select the
        # whole text line, per QsciScintilla's behaviour. It is up to the
        # click handler to ignore clicks on this margin: self.connect_margin.
        self.setMarginWidth(4, 8)
        self.setMarginSensitivity(4, True)
        # Indicators
        self.setIndicatorDrawUnder(True)
        for type_ in self.check_indicators:
            self.indicatorDefine(
                self.SquiggleIndicator, self.check_indicators[type_]["id"]
            )
        for type_ in self.search_indicators:
            self.indicatorDefine(
                self.StraightBoxIndicator, self.search_indicators[type_]["id"]
            )
        self.indicatorDefine(self.FullBoxIndicator, self.DEBUG_INDICATOR)
        self.setAnnotationDisplay(self.AnnotationBoxed)
        self.selectionChanged.connect(self.selection_change_listener)
        self.set_zoom()

    def connect_margin(self, func):
        """
        Connect clicking the margin to the passed in handler function, via a
        filtering handler that ignores clicks on margin 4.
        """
        # Margin 4 motivation in self.configure comments.
        def func_ignoring_margin_4(margin, line, modifiers):
            if margin != 4:
                func(margin, line, modifiers)

        self.marginClicked.connect(func_ignoring_margin_4)

    def set_theme(self, theme=DayTheme):
        """
        Connect the theme to a lexer and return the lexer for the editor to
        apply to the script text.
        """
        theme.apply_to(self.lexer)
        self.lexer.setDefaultPaper(theme.Paper)
        self.setCaretForegroundColor(theme.Caret)
        self.setIndicatorForegroundColor(
            theme.IndicatorError, self.check_indicators["error"]["id"]
        )
        self.setIndicatorForegroundColor(
            theme.IndicatorStyle, self.check_indicators["style"]["id"]
        )
        self.setIndicatorForegroundColor(
            theme.DebugStyle, self.DEBUG_INDICATOR
        )
        for type_ in self.search_indicators:
            self.setIndicatorForegroundColor(
                theme.IndicatorWordMatch, self.search_indicators[type_]["id"]
            )
        self.setMarkerBackgroundColor(
            theme.BreakpointMarker, self.BREAKPOINT_MARKER
        )
        self.setAutoCompletionThreshold(2)
        self.setAutoCompletionSource(QsciScintilla.AcsAll)
        self.setLexer(self.lexer)
        self.setMarginsBackgroundColor(theme.Margin)
        self.setMarginsForegroundColor(theme.Caret)
        self.setMatchedBraceBackgroundColor(theme.BraceBackground)
        self.setMatchedBraceForegroundColor(theme.BraceForeground)
        self.setUnmatchedBraceBackgroundColor(theme.UnmatchedBraceBackground)
        self.setUnmatchedBraceForegroundColor(theme.UnmatchedBraceForeground)

    def set_api(self, api_definitions):
        """
        Sets the API entries for tooltips, calltips and the like.
        """
        self.api = QsciAPIs(self.lexer)
        for entry in api_definitions:
            self.api.add(entry)
        self.api.prepare()

    def set_zoom(self, size="m"):
        """
        Sets the font zoom to the specified base point size for all fonts given
        a t-shirt size.
        """
        sizes = {
            "xs": -4,
            "s": -2,
            "m": 1,
            "l": 4,
            "xl": 8,
            "xxl": 16,
            "xxxl": 48,
        }
        self.zoomTo(sizes[size])
        margins = {
            "xs": 30,
            "s": 35,
            "m": 45,
            "l": 50,
            "xl": 60,
            "xxl": 75,
            "xxxl": 85,
        }
        # Make the margin left of line numbers follow zoom level
        self.setMarginWidth(0, margins[size])
        # Make margins around debugger-marker follow zoom level
        self.setMarginWidth(1, margins[size] * 0.25)
        self.setMarginWidth(4, margins[size] * 0.1)

    @property
    def label(self):
        """
        The label associated with this editor widget (usually the filename of
        the script we're editing).
        """
        if self.path:
            label = os.path.basename(self.path)
        else:
            label = _("untitled")
        return label

    @property
    def title(self):
        """
        The title associated with this editor widget (usually the filename of
        the script we're editing).

        If the script has been modified since it was last saved, the label will
        end with an asterisk.
        """
        if self.isModified():
            return self.label + " •"
        return self.label

    def reset_annotations(self):
        """
        Clears all the assets (indicators, annotations and markers).
        """
        self.clearAnnotations()
        self.markerDeleteAll()
        self.reset_search_indicators()
        self.reset_check_indicators()

    def reset_check_indicators(self):
        """
        Clears all the text indicators related to the check code functionality.
        """
        for indicator in self.check_indicators:
            for _, markers in self.check_indicators[indicator][
                "markers"
            ].items():
                line_no = markers[0]["line_no"]  # All markers on same line.
                self.clearIndicatorRange(
                    line_no,
                    0,
                    line_no,
                    999999,
                    self.check_indicators[indicator]["id"],
                )
            self.check_indicators[indicator]["markers"] = {}

    def reset_search_indicators(self):
        """
        Clears all the text indicators from the search functionality.
        """
        for indicator in self.search_indicators:
            for position in self.search_indicators[indicator]["positions"]:
                self.clearIndicatorRange(
                    position["line_start"],
                    position["col_start"],
                    position["line_end"],
                    position["col_end"],
                    self.search_indicators[indicator]["id"],
                )
            self.search_indicators[indicator]["positions"] = []

    def annotate_code(self, feedback, annotation_type="error"):
        """
        Given a list of annotations add them to the editor pane so the user can
        act upon them.
        """
        indicator = self.check_indicators[annotation_type]
        for line_no, messages in feedback.items():
            indicator["markers"][line_no] = messages
            for message in messages:
                col = message.get("column", 0)
                if col:
                    col_start = col - 1
                    col_end = col + 1
                    self.fillIndicatorRange(
                        line_no, col_start, line_no, col_end, indicator["id"]
                    )
        if feedback:
            # Ensure the first line with a problem is visible.
            first_problem_line = sorted(feedback.keys())[0]
            self.ensureLineVisible(first_problem_line)

    def debugger_at_line(self, line):
        """
        Set the line to be highlighted with the DEBUG_INDICATOR.
        """
        self.reset_debugger_highlight()
        # Calculate the line length & account for \r\n giving ObOE.
        line_length = len(self.text(line).rstrip())
        self.fillIndicatorRange(
            line, 0, line, line_length, self.DEBUG_INDICATOR
        )
        self.ensureLineVisible(line)

    def reset_debugger_highlight(self):
        """
        Reset all the lines so the DEBUG_INDICATOR is no longer displayed.

        We need to check each line since there's no way to tell what the
        currently highlighted line is. This approach also has the advantage of
        resetting the *whole* editor pane.
        """
        for i in range(self.lines()):
            line_length = len(self.text(i))
            self.clearIndicatorRange(
                i, 0, i, line_length, self.DEBUG_INDICATOR
            )

    def show_annotations(self):
        """
        Display all the messages to be annotated to the code.
        """
        lines = defaultdict(list)
        for indicator in self.check_indicators:
            markers = self.check_indicators[indicator]["markers"]
            for k, marker_list in markers.items():
                for m in marker_list:
                    lines[m["line_no"]].append("\u2191 " + m["message"])
        for line, messages in lines.items():
            text = "\n".join(messages).strip()
            if text:
                self.annotate(line, text, self.annotationDisplay())

    def find_next_match(
        self,
        text,
        from_line=-1,
        from_col=-1,
        case_sensitive=True,
        wrap_around=True,
    ):
        """
        Finds the next text match from the current cursor, or the given
        position, and selects it (the automatic selection is the only available
        QsciScintilla behaviour).
        Returns True if match found, False otherwise.
        """
        return self.findFirst(
            text,  # Text to find,
            False,  # Treat as regular expression
            case_sensitive,  # Case sensitive search
            True,  # Whole word matches only
            wrap_around,  # Wrap search
            forward=True,  # Forward search
            line=from_line,  # -1 starts at current position
            index=from_col,  # -1 starts at current position
            show=False,  # Unfolds found text
            posix=False,
        )  # More POSIX compatible RegEx

    def range_from_positions(self, start_position, end_position):
        """Given a start-end pair, such as are provided by a regex match,
        return the corresponding Scintilla line-offset pairs which are
        used for searches, indicators etc.

        NOTE: Arguments must be byte offsets into the underlying text bytes.
        """
        start_line, start_offset = self.lineIndexFromPosition(start_position)
        end_line, end_offset = self.lineIndexFromPosition(end_position)
        return start_line, start_offset, end_line, end_offset

    def highlight_selected_matches(self):
        """
        Checks the current selection, if it is a single word it then searches
        and highlights all matches.

        Since we're interested in exactly one word:
        * Ignore an empty selection
        * Ignore anything which spans more than one line
        * Ignore more than one word
        * Ignore anything less than one word
        """
        selected_range = line0, col0, line1, col1 = self.getSelection()
        #
        # If there's no selection, do nothing
        #
        if selected_range == (-1, -1, -1, -1):
            return

        #
        # Ignore anything which spans two or more lines
        #
        if line0 != line1:
            return

        #
        # Ignore if no text is selected or the selected text is not at most one
        # valid identifier-type word.
        #
        selected_text = self.selectedText()
        if not RE_VALID_WORD.match(selected_text):
            return

        #
        # Ignore anything which is not a whole word.
        # NB Although Scintilla defines a SCI_ISRANGEWORD message,
        # it's not exposed by QSciScintilla. Instead, we
        # ask Scintilla for the start end end position of
        # the word we're in and test whether our range end points match
        # those or not.
        #
        pos0 = self.positionFromLineIndex(line0, col0)
        word_start_pos = self.SendScintilla(
            QsciScintilla.SCI_WORDSTARTPOSITION, pos0, 1
        )
        _, start_offset = self.lineIndexFromPosition(word_start_pos)
        if col0 != start_offset:
            return

        pos1 = self.positionFromLineIndex(line1, col1)
        word_end_pos = self.SendScintilla(
            QsciScintilla.SCI_WORDENDPOSITION, pos1, 1
        )
        _, end_offset = self.lineIndexFromPosition(word_end_pos)
        if col1 != end_offset:
            return

        #
        # For each matching word within the editor text, add it to
        # the list of highlighted indicators and fill it according
        # to the current theme.
        #
        indicators = self.search_indicators["selection"]
        encoding = "utf8" if self.isUtf8() else "latin1"
        text_bytes = self.text().encode(encoding)
        selected_text_bytes = selected_text.encode(encoding)
        for match in re.finditer(selected_text_bytes, text_bytes):
            range = self.range_from_positions(*match.span())
            #
            # Don't highlight the text we've selected
            #
            if range == selected_range:
                continue

            line_start, col_start, line_end, col_end = range
            indicators["positions"].append(
                {
                    "line_start": line_start,
                    "col_start": col_start,
                    "line_end": line_end,
                    "col_end": col_end,
                }
            )
            self.fillIndicatorRange(
                line_start, col_start, line_end, col_end, indicators["id"]
            )

    def selection_change_listener(self):
        """
        Runs every time the text selection changes. This could get triggered
        multiple times while the mouse click is down, even if selection has not
        changed in itself.
        If there is a new selection is passes control to
        highlight_selected_matches.
        """
        # Get the current selection, exit if it has not changed
        line_from, index_from, line_to, index_to = self.getSelection()
        if (
            self.previous_selection["col_end"] != index_to
            or self.previous_selection["col_start"] != index_from
            or self.previous_selection["line_start"] != line_from
            or self.previous_selection["line_end"] != line_to
        ):
            self.previous_selection["line_start"] = line_from
            self.previous_selection["col_start"] = index_from
            self.previous_selection["line_end"] = line_to
            self.previous_selection["col_end"] = index_to
            # Highlight matches
            self.reset_search_indicators()
            self.highlight_selected_matches()

    def toggle_line(self, raw_line):
        """
        Given a raw_line, will return the toggled version of it.
        """
        clean_line = raw_line.strip()
        if not clean_line or clean_line.startswith("##"):
            # Ignore whitespace-only lines and compact multi-commented lines
            return raw_line

        if clean_line.startswith("#"):
            # It's a comment line, so replace only the first "# " or "#":
            if clean_line.startswith("# "):
                return raw_line.replace("# ", "", 1)
            else:
                return raw_line.replace("#", "", 1)
        else:
            # It's a normal line of code.
            return "# " + raw_line

    def toggle_comments(self):
        """
        Iterate through the selected lines and toggle their comment/uncomment
        state. So, lines that are not comments become comments and vice versa.
        """
        if self.hasSelectedText():
            # Toggle currently selected text.
            logger.info("Toggling comments")
            line_from, index_from, line_to, index_to = self.getSelection()
            selected_text = self.selectedText()
            lines = selected_text.split("\n")
            toggled_lines = []
            for line in lines:
                toggled_lines.append(self.toggle_line(line))
            new_text = "\n".join(toggled_lines)
            self.replaceSelectedText(new_text)
            # Ensure the new text is also selected.
            last_newline = toggled_lines[-1]
            last_oldline = lines[-1]

            # Adjust the selection based on whether the last line got
            # longer, shorter, or stayed the same
            delta = len(last_newline) - len(last_oldline)
            index_to += delta
            self.setSelection(line_from, index_from, line_to, index_to)
        else:
            # Toggle the line currently containing the cursor.
            line_number, column = self.getCursorPosition()
            logger.info("Toggling line {}".format(line_number))
            # Replace CRLF line endings that we add when run on Windows
            line_content = self.text(line_number).replace("\r\n", "\n")
            new_line = self.toggle_line(line_content)
            self.setSelection(line_number, 0, line_number, len(line_content))
            self.replaceSelectedText(new_line)
            self.setSelection(line_number, 0, line_number, len(new_line) - 1)
Example #29
0
class APIs(QObject):
    """
    Class implementing an API storage entity.
    
    @signal apiPreparationFinished() emitted after the API preparation has
        finished
    @signal apiPreparationCancelled() emitted after the API preparation has
        been cancelled
    @signal apiPreparationStarted() emitted after the API preparation has
        started
    """
    apiPreparationFinished = pyqtSignal()
    apiPreparationCancelled = pyqtSignal()
    apiPreparationStarted = pyqtSignal()

    def __init__(self,
                 language,
                 projectType="",
                 forPreparation=False,
                 parent=None):
        """
        Constructor
        
        @param language language of the APIs object
        @type str
        @param projectType type of the project
        @type str
        @param forPreparation flag indicating this object is just needed
            for a preparation process
        @type bool
        @param parent reference to the parent object
        @type QObject
        """
        super(APIs, self).__init__(parent)
        if projectType:
            self.setObjectName("APIs_{0}_{1}".format(language, projectType))
        else:
            self.setObjectName("APIs_{0}".format(language))

        self.__inPreparation = False
        self.__language = language
        self.__projectType = projectType
        self.__forPreparation = forPreparation
        self.__lexer = Lexers.getLexer(self.__language)
        self.__apifiles = Preferences.getEditorAPI(self.__language,
                                                   self.__projectType)
        self.__apifiles.sort()
        if self.__lexer is None:
            self.__apis = None
        else:
            self.__apis = QsciAPIs(self.__lexer)
            self.__apis.apiPreparationFinished.connect(
                self.__apiPreparationFinished)
            self.__apis.apiPreparationCancelled.connect(
                self.__apiPreparationCancelled)
            self.__apis.apiPreparationStarted.connect(
                self.__apiPreparationStarted)
            self.__loadAPIs()

    def __loadAPIs(self):
        """
        Private method to load the APIs.
        """
        if self.__apis.isPrepared():
            # load a prepared API file
            if (not self.__forPreparation
                    and Preferences.getEditor("AutoPrepareAPIs")):
                self.prepareAPIs()
            self.__apis.loadPrepared(self.__preparedName())
        else:
            # load the raw files and prepare the API file
            if (not self.__forPreparation
                    and Preferences.getEditor("AutoPrepareAPIs")):
                self.prepareAPIs(ondemand=True)

    def reloadAPIs(self):
        """
        Public method to reload the API information.
        """
        if (not self.__forPreparation
                and Preferences.getEditor("AutoPrepareAPIs")):
            self.prepareAPIs()
        self.__loadAPIs()

    def getQsciAPIs(self):
        """
        Public method to get a reference to QsciAPIs object.
        
        @return reference to the QsciAPIs object (QsciAPIs)
        """
        if (not self.__forPreparation
                and Preferences.getEditor("AutoPrepareAPIs")):
            self.prepareAPIs()
        return self.__apis

    def isEmpty(self):
        """
        Public method to check, if the object has API files configured.
        
        @return flag indicating no API files have been configured (boolean)
        """
        return len(self.__apifiles) == 0

    def __apiPreparationFinished(self):
        """
        Private method called to save an API, after it has been prepared.
        """
        self.__apis.savePrepared(self.__preparedName())
        self.__inPreparation = False
        self.apiPreparationFinished.emit()

    def __apiPreparationCancelled(self):
        """
        Private method called, after the API preparation process has been
        cancelled.
        """
        self.__inPreparation = False
        self.apiPreparationCancelled.emit()

    def __apiPreparationStarted(self):
        """
        Private method called, when the API preparation process started.
        """
        self.__inPreparation = True
        self.apiPreparationStarted.emit()

    def prepareAPIs(self, ondemand=False, rawList=None):
        """
        Public method to prepare the APIs if necessary.
        
        @keyparam ondemand flag indicating a requested preparation (boolean)
        @keyparam rawList list of raw API files (list of strings)
        """
        if self.__apis is None or self.__inPreparation:
            return

        needsPreparation = False
        if ondemand:
            needsPreparation = True
        else:
            # check, if a new preparation is necessary
            preparedAPIs = self.__preparedName()
            if preparedAPIs:
                preparedAPIsInfo = QFileInfo(preparedAPIs)
                if not preparedAPIsInfo.exists():
                    needsPreparation = True
                else:
                    preparedAPIsTime = preparedAPIsInfo.lastModified()
                    apifiles = sorted(
                        Preferences.getEditorAPI(self.__language,
                                                 self.__projectType))
                    if self.__apifiles != apifiles:
                        needsPreparation = True
                    for apifile in apifiles:
                        if (QFileInfo(apifile).lastModified() >
                                preparedAPIsTime):
                            needsPreparation = True
                            break

        if needsPreparation:
            # do the preparation
            self.__apis.clear()
            if rawList:
                apifiles = rawList
            else:
                apifiles = Preferences.getEditorAPI(self.__language,
                                                    self.__projectType)
            for apifile in apifiles:
                self.__apis.load(apifile)
            self.__apis.prepare()
            self.__apifiles = apifiles

    def cancelPreparation(self):
        """
        Public slot to cancel the APIs preparation.
        """
        self.__apis and self.__apis.cancelPreparation()

    def installedAPIFiles(self):
        """
        Public method to get a list of installed API files.
        
        @return list of installed API files (list of strings)
        """
        if self.__apis is not None:
            if Globals.isWindowsPlatform():
                qsciPath = os.path.join(Globals.getPyQt5ModulesDirectory(),
                                        "qsci")
                if os.path.exists(qsciPath):
                    # it's the installer
                    if self.__lexer.lexerName() is not None:
                        apidir = os.path.join(qsciPath, "api",
                                              self.__lexer.lexerName())
                        fnames = []
                        filist = QDir(apidir).entryInfoList(["*.api"],
                                                            QDir.Files,
                                                            QDir.IgnoreCase)
                        for fi in filist:
                            fnames.append(fi.absoluteFilePath())
                        return fnames
                    else:
                        return []

            return self.__apis.installedAPIFiles()
        else:
            return []

    def __preparedName(self):
        """
        Private method returning the default name of a prepared API file.
        
        @return complete filename for the Prepared APIs file (string)
        """
        apisDir = os.path.join(Globals.getConfigDir(), "APIs")
        if self.__apis is not None:
            if self.__projectType:
                filename = "{0}_{1}.pap".format(self.__language,
                                                self.__projectType)
            else:
                filename = "{0}.pap".format(self.__language)
            return os.path.join(apisDir, filename)
        else:
            return ""
Example #30
0
class APIs(QObject):
    """
    Class implementing an API storage entity.
    
    @signal apiPreparationFinished() emitted after the API preparation has
        finished
    @signal apiPreparationCancelled() emitted after the API preparation has
        been cancelled
    @signal apiPreparationStarted() emitted after the API preparation has
        started
    """
    apiPreparationFinished = pyqtSignal()
    apiPreparationCancelled = pyqtSignal()
    apiPreparationStarted = pyqtSignal()
    
    def __init__(self, language, forPreparation=False, parent=None):
        """
        Constructor
        
        @param language language of the APIs object (string)
        @param forPreparation flag indicating this object is just needed
            for a preparation process (boolean)
        @param parent reference to the parent object (QObject)
        """
        super(APIs, self).__init__(parent)
        self.setObjectName("APIs_{0}".format(language))
        
        self.__inPreparation = False
        self.__language = language
        self.__forPreparation = forPreparation
        self.__lexer = Lexers.getLexer(self.__language)
        self.__apifiles = Preferences.getEditorAPI(self.__language)
        self.__apifiles.sort()
        if self.__lexer is None:
            self.__apis = None
        else:
            self.__apis = QsciAPIs(self.__lexer)
            self.__apis.apiPreparationFinished.connect(
                self.__apiPreparationFinished)
            self.__apis.apiPreparationCancelled.connect(
                self.__apiPreparationCancelled)
            self.__apis.apiPreparationStarted.connect(
                self.__apiPreparationStarted)
            self.__loadAPIs()
        
    def __loadAPIs(self):
        """
        Private method to load the APIs.
        """
        if self.__apis.isPrepared():
            # load a prepared API file
            if not self.__forPreparation and \
                    Preferences.getEditor("AutoPrepareAPIs"):
                self.prepareAPIs()
            self.__apis.loadPrepared()
        else:
            # load the raw files and prepare the API file
            if not self.__forPreparation and \
                    Preferences.getEditor("AutoPrepareAPIs"):
                self.prepareAPIs(ondemand=True)
    
    def reloadAPIs(self):
        """
        Public method to reload the API information.
        """
        if not self.__forPreparation and \
                Preferences.getEditor("AutoPrepareAPIs"):
            self.prepareAPIs()
        self.__loadAPIs()
    
    def getQsciAPIs(self):
        """
        Public method to get a reference to QsciAPIs object.
        
        @return reference to the QsciAPIs object (QsciAPIs)
        """
        if not self.__forPreparation and \
                Preferences.getEditor("AutoPrepareAPIs"):
            self.prepareAPIs()
        return self.__apis
    
    def isEmpty(self):
        """
        Public method to check, if the object has API files configured.
        
        @return flag indicating no API files have been configured (boolean)
        """
        return len(self.__apifiles) == 0
    
    def __apiPreparationFinished(self):
        """
        Private method called to save an API, after it has been prepared.
        """
        self.__apis.savePrepared()
        self.__inPreparation = False
        self.apiPreparationFinished.emit()
    
    def __apiPreparationCancelled(self):
        """
        Private method called, after the API preparation process has been
        cancelled.
        """
        self.__inPreparation = False
        self.apiPreparationCancelled.emit()
    
    def __apiPreparationStarted(self):
        """
        Private method called, when the API preparation process started.
        """
        self.__inPreparation = True
        self.apiPreparationStarted.emit()
    
    def prepareAPIs(self, ondemand=False, rawList=None):
        """
        Public method to prepare the APIs if necessary.
        
        @keyparam ondemand flag indicating a requested preparation (boolean)
        @keyparam rawList list of raw API files (list of strings)
        """
        if self.__apis is None or self.__inPreparation:
            return
        
        needsPreparation = False
        if ondemand:
            needsPreparation = True
        else:
            # check, if a new preparation is necessary
            preparedAPIs = self.__defaultPreparedName()
            if preparedAPIs:
                preparedAPIsInfo = QFileInfo(preparedAPIs)
                if not preparedAPIsInfo.exists():
                    needsPreparation = True
                else:
                    preparedAPIsTime = preparedAPIsInfo.lastModified()
                    apifiles = sorted(
                        Preferences.getEditorAPI(self.__language))
                    if self.__apifiles != apifiles:
                        needsPreparation = True
                    for apifile in apifiles:
                        if QFileInfo(apifile).lastModified() > \
                                preparedAPIsTime:
                            needsPreparation = True
                            break
        
        if needsPreparation:
            # do the preparation
            self.__apis.clear()
            if rawList:
                apifiles = rawList
            else:
                apifiles = Preferences.getEditorAPI(self.__language)
            for apifile in apifiles:
                self.__apis.load(apifile)
            self.__apis.prepare()
            self.__apifiles = apifiles
    
    def cancelPreparation(self):
        """
        Public slot to cancel the APIs preparation.
        """
        self.__apis and self.__apis.cancelPreparation()
    
    def installedAPIFiles(self):
        """
        Public method to get a list of installed API files.
        
        @return list of installed API files (list of strings)
        """
        if self.__apis is not None:
            if Globals.isWindowsPlatform():
                qsciPath = os.path.join(
                    Globals.getPyQt5ModulesDirectory(), "qsci")
                if os.path.exists(qsciPath):
                    # it's the installer
                    if self.__lexer.lexerName() is not None:
                        apidir = os.path.join(qsciPath, "api",
                                              self.__lexer.lexerName())
                        fnames = []
                        filist = QDir(apidir).entryInfoList(
                            ["*.api"], QDir.Files, QDir.IgnoreCase)
                        for fi in filist:
                            fnames.append(fi.absoluteFilePath())
                        return fnames
                    else:
                        return []
            
            return self.__apis.installedAPIFiles()
        else:
            return []
    
    def __defaultPreparedName(self):
        """
        Private method returning the default name of a prepared API file.
        
        @return complete filename for the Prepared APIs file (string)
        """
        if self.__apis is not None:
            return self.__apis.defaultPreparedName()
        else:
            return ""
    def _load_code_editor_settings(self):
        """
		Load settings on the code editor like, font style, margins, scroll, etc.
		Based on the example from http://eli.thegreenplace.net/2011/04/01/sample-using-qscintilla-with-pyqt/
		"""

        item = self.form.font_size.currentText()
        size = int(item)

        # Set the default font
        font = QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(size)
        self._code_editor.setFont(font)
        self._code_editor.setMarginsFont(font)

        # Margin 0 is used for line numbers
        fontmetrics = QFontMetrics(font)
        self._code_editor.setMarginsFont(font)
        self._code_editor.setMarginWidth(0, fontmetrics.width("00000") + 6)
        self._code_editor.setMarginLineNumbers(0, True)
        self._code_editor.setMarginsBackgroundColor(QColor("#cccccc"))

        # Clickable margin 1 for showing markers
        self._code_editor.setMarginSensitivity(1, True)
        self._code_editor.marginClicked.connect(self.on_margin_clicked)
        self._code_editor.markerDefine(QsciScintilla.RightArrow,
                                       self.ARROW_MARKER_NUM)
        self._code_editor.setMarkerBackgroundColor(QColor("#ee1111"),
                                                   self.ARROW_MARKER_NUM)

        # Detect changes to text
        self._code_editor.modificationChanged.connect(
            self.on_modification_changed)

        # Brace matching: enable for a brace immediately before or after the current position
        self._code_editor.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Current line visible with special background color
        self._code_editor.setCaretLineVisible(True)
        self._code_editor.setCaretLineBackgroundColor(QColor("#ffe4e4"))

        # Set Python lexer
        # Set style for Python comments (style number 1) to a fixed-width Courier.
        lexer = self.lexer()
        lexer.setDefaultFont(font)
        self._code_editor.setLexer(lexer)
        self._code_editor.setIndentationWidth(4)
        # self._code_editor.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')
        self._code_editor.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1)

        # Don't want to see the horizontal scrollbar at all
        # Use raw message to Scintilla here (all messages are documented here: http://www.scintilla.org/ScintillaDoc.html)
        self._code_editor.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)

        self._lexer_obj = lexer
        self.qsci_api = QsciAPIs(self._lexer_obj)
        ## Add autocompletion strings
        self.qsci_api.add("aLongString")
        self.qsci_api.add("aLongerString")
        self.qsci_api.add("aDifferentString")
        self.qsci_api.add("sOmethingElse")
        ## Compile the api for use in the lexer
        self.qsci_api.prepare()
        self._code_editor.setAutoCompletionThreshold(1)
        self._code_editor.setAutoCompletionSource(QsciScintilla.AcsAll)
Example #32
0
    def __init__(self, parent=None, fileName=None, readOnlyFiles=[]):
        '''
        Constructor
        '''
        super(CppEditor, self).__init__(parent)
        self.parent = parent
        self.roFiles = readOnlyFiles

        self.setAcceptDrops(False) # drag&drop is on its parent

        # Set the default font
        font = QtGui.QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.setMarginsFont(font)

        # C/C++ lexer
        self.lexer = QsciLexerCPP(self,  True)
        self.lexer.setDefaultFont(font)
        self.libraryAPIs = QsciAPIs(QsciLexerCPP(self,True))
        self.setLexer(self.lexer)
        #self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')

        # Auto-indent
        self.setTabWidth(4)
        #self.setIndentationsUseTabs(False)
        self.setAutoIndent(True)

        # Current line visible with special background color
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QtGui.QColor("#ffe4e4"))

        # Enable brace matching
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Enable folding visual- use boxes
        self.setFolding(QsciScintilla.BoxedTreeFoldStyle)

        # show line numbers
        fontmetrics = QtGui.QFontMetrics(font)
        self.setMarginsFont(font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 4)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QtGui.QColor("#ccccee"))

        # not too small
        self.setMinimumSize(400, 200)

        # set the length of the string before the editor tries to auto-complete
        self.setAutoCompletionThreshold(3)
        # tell the editor we are using a QsciAPI for the auto-completion
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
        # removed remaining right side characters from the current cursor
        self.setAutoCompletionReplaceWord(True)

        # "CTRL+Space" autocomplete
        self.shortcut_ctrl_space = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Space"), self)
        self.shortcut_ctrl_space.activated.connect(self.autoCompleteFromAll)

        if fileName:
            self.curFile = fileName
            self.loadFile(fileName)
            self.isUntitled = False
        else:
            self.curFile = PROJECT_NONAME + USER_CODE_EXT
            self.setText( __default_content__ )
            self.isUntitled = True

        self.updateApiKeywords()
        self.isModified = False
        self.textChanged.connect(self.onTextChanged )
Example #33
0
class EditorPane(QsciScintilla):
    """
    Represents the text editor.
    """
    def __init__(self, path, text):
        super().__init__()
        self.path = path
        self.setText(text)
        self.check_indicators = {  # IDs are arbitrary
            'error': {
                'id': 19,
                'markers': {}
            },
            'style': {
                'id': 20,
                'markers': {}
            }
        }
        self.BREAKPOINT_MARKER = 23  # Arbitrary
        self.search_indicators = {'selection': {'id': 21, 'positions': []}}
        self.previous_selection = {
            'line_start': 0,
            'col_start': 0,
            'line_end': 0,
            'col_end': 0
        }
        self.lexer = PythonLexer()
        self.api = None
        self.has_annotations = False
        self.setModified(False)
        self.breakpoint_lines = set()
        self.configure()

    def configure(self):
        """
        Set up the editor component.
        """
        # Font information
        font = Font().load()
        self.setFont(font)
        # Generic editor settings
        self.setUtf8(True)
        self.setAutoIndent(True)
        self.setIndentationsUseTabs(False)
        self.setIndentationWidth(4)
        self.setIndentationGuides(True)
        self.setBackspaceUnindents(True)
        self.setTabWidth(4)
        self.setEdgeColumn(79)
        self.setMarginLineNumbers(0, True)
        self.setMarginWidth(0, 50)
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
        self.set_theme()
        # Markers and indicators
        self.setMarginSensitivity(0, True)
        self.markerDefine(self.Circle, self.BREAKPOINT_MARKER)
        self.setMarginSensitivity(1, True)
        self.setIndicatorDrawUnder(True)
        for type_ in self.check_indicators:
            self.indicatorDefine(self.SquiggleIndicator,
                                 self.check_indicators[type_]['id'])
        for type_ in self.search_indicators:
            self.indicatorDefine(self.StraightBoxIndicator,
                                 self.search_indicators[type_]['id'])
        self.setAnnotationDisplay(self.AnnotationBoxed)
        self.selectionChanged.connect(self.selection_change_listener)

    def connect_margin(self, func):
        """
        Connect clicking the margin to the passed in handler function.
        """
        self.marginClicked.connect(func)

    def set_theme(self, theme=DayTheme):
        """
        Connect the theme to a lexer and return the lexer for the editor to
        apply to the script text.
        """
        theme.apply_to(self.lexer)
        self.lexer.setDefaultPaper(theme.Paper)
        self.setCaretForegroundColor(theme.Caret)
        self.setMarginsBackgroundColor(theme.Margin)
        self.setMarginsForegroundColor(theme.Caret)
        self.setIndicatorForegroundColor(theme.IndicatorError,
                                         self.check_indicators['error']['id'])
        self.setIndicatorForegroundColor(theme.IndicatorStyle,
                                         self.check_indicators['style']['id'])
        for type_ in self.search_indicators:
            self.setIndicatorForegroundColor(
                theme.IndicatorWordMatch, self.search_indicators[type_]['id'])
        self.setMarkerBackgroundColor(theme.BreakpointMarker,
                                      self.BREAKPOINT_MARKER)
        self.setAutoCompletionThreshold(2)
        self.setAutoCompletionSource(QsciScintilla.AcsAll)
        self.setLexer(self.lexer)
        self.setMatchedBraceBackgroundColor(theme.BraceBackground)
        self.setMatchedBraceForegroundColor(theme.BraceForeground)
        self.setUnmatchedBraceBackgroundColor(theme.UnmatchedBraceBackground)
        self.setUnmatchedBraceForegroundColor(theme.UnmatchedBraceForeground)

    def set_api(self, api_definitions):
        """
        Sets the API entries for tooltips, calltips and the like.
        """
        self.api = QsciAPIs(self.lexer)
        for entry in api_definitions:
            self.api.add(entry)
        self.api.prepare()

    @property
    def label(self):
        """
        The label associated with this editor widget (usually the filename of
        the script we're editing).

        If the script has been modified since it was last saved, the label will
        end with an asterisk.
        """
        if self.path:
            label = os.path.basename(self.path)
        else:
            label = 'untitled'
        # Add an asterisk to indicate that the file remains unsaved.
        if self.isModified():
            return label + ' *'
        else:
            return label

    def reset_annotations(self):
        """
        Clears all the assets (indicators, annotations and markers).
        """
        self.clearAnnotations()
        self.markerDeleteAll()
        self.reset_search_indicators()
        self.reset_check_indicators()

    def reset_check_indicators(self):
        """
        Clears all the text indicators related to the check code functionality.
        """
        for indicator in self.check_indicators:
            for _, markers in \
                    self.check_indicators[indicator]['markers'].items():
                line_no = markers[0]['line_no']  # All markers on same line.
                self.clearIndicatorRange(
                    line_no, 0, line_no, 999999,
                    self.check_indicators[indicator]['id'])
            self.check_indicators[indicator]['markers'] = {}

    def reset_search_indicators(self):
        """
        Clears all the text indicators from the search functionality.
        """
        for indicator in self.search_indicators:
            for position in self.search_indicators[indicator]['positions']:
                self.clearIndicatorRange(
                    position['line_start'], position['col_start'],
                    position['line_end'], position['col_end'],
                    self.search_indicators[indicator]['id'])
            self.search_indicators[indicator]['positions'] = []

    def annotate_code(self, feedback, annotation_type='error'):
        """
        Given a list of annotations add them to the editor pane so the user can
        act upon them.
        """
        indicator = self.check_indicators[annotation_type]
        for line_no, messages in feedback.items():
            indicator['markers'][line_no] = messages
            for message in messages:
                col = message.get('column', 0)
                if col:
                    col_start = col - 1
                    col_end = col + 1
                    self.fillIndicatorRange(line_no, col_start, line_no,
                                            col_end, indicator['id'])

    def show_annotations(self):
        """
        Display all the messages to be annotated to the code.
        """
        lines = defaultdict(list)
        for indicator in self.check_indicators:
            markers = self.check_indicators[indicator]['markers']
            for k, marker_list in markers.items():
                for m in marker_list:
                    lines[m['line_no']].append(m['message'])
        for line, messages in lines.items():
            text = '\n'.join(messages).strip()
            if text:
                self.annotate(line, text, self.annotationDisplay())

    def find_next_match(self,
                        text,
                        from_line=-1,
                        from_col=-1,
                        case_sensitive=True,
                        wrap_around=True):
        """
        Finds the next text match from the current cursor, or the given
        position, and selects it (the automatic selection is the only available
        QsciScintilla behaviour).
        Returns True if match found, False otherwise.
        """
        return self.findFirst(
            text,  # Text to find,
            False,  # Treat as regular expression
            case_sensitive,  # Case sensitive search
            True,  # Whole word matches only
            wrap_around,  # Wrap search
            forward=True,  # Forward search
            line=from_line,  # -1 starts at current position
            index=from_col,  # -1 starts at current position
            show=False,  # Unfolds found text
            posix=False)  # More POSIX compatible RegEx

    def range_from_positions(self, start_position, end_position):
        """Given a start-end pair, such as are provided by a regex match,
        return the corresponding Scintilla line-offset pairs which are
        used for searches, indicators etc.

        FIXME: Not clear whether the Scintilla conversions are expecting
        bytes or characters (ie codepoints)
        """
        start_line, start_offset = self.lineIndexFromPosition(start_position)
        end_line, end_offset = self.lineIndexFromPosition(end_position)
        return start_line, start_offset, end_line, end_offset

    def highlight_selected_matches(self):
        """
        Checks the current selection, if it is a single word it then searches
        and highlights all matches.

        Since we're interested in exactly one word:
        * Ignore an empty selection
        * Ignore anything which spans more than one line
        * Ignore more than one word
        * Ignore anything less than one word
        """
        selected_range = line0, col0, line1, col1 = self.getSelection()
        #
        # If there's no selection, do nothing
        #
        if selected_range == (-1, -1, -1, -1):
            return

        #
        # Ignore anything which spans two or more lines
        #
        if line0 != line1:
            return

        #
        # Ignore if no text is selected or the selected text is not at most one
        # valid identifier-type word.
        #
        selected_text = self.selectedText()
        if not RE_VALID_WORD.match(selected_text):
            return

        #
        # Ignore anything which is not a whole word.
        # NB Although Scintilla defines a SCI_ISRANGEWORD message,
        # it's not exposed by QSciScintilla. Instead, we
        # ask Scintilla for the start end end position of
        # the word we're in and test whether our range end points match
        # those or not.
        #
        pos0 = self.positionFromLineIndex(line0, col0)
        word_start_pos = self.SendScintilla(
            QsciScintilla.SCI_WORDSTARTPOSITION, pos0, 1)
        _, start_offset = self.lineIndexFromPosition(word_start_pos)
        if col0 != start_offset:
            return

        pos1 = self.positionFromLineIndex(line1, col1)
        word_end_pos = self.SendScintilla(QsciScintilla.SCI_WORDENDPOSITION,
                                          pos1, 1)
        _, end_offset = self.lineIndexFromPosition(word_end_pos)
        if col1 != end_offset:
            return

        #
        # For each matching word within the editor text, add it to
        # the list of highlighted indicators and fill it according
        # to the current theme.
        #
        indicators = self.search_indicators['selection']
        text = self.text()
        for match in re.finditer(selected_text, text):
            range = self.range_from_positions(*match.span())
            #
            # Don't highlight the text we've selected
            #
            if range == selected_range:
                continue

            line_start, col_start, line_end, col_end = range
            indicators['positions'].append({
                'line_start': line_start,
                'col_start': col_start,
                'line_end': line_end,
                'col_end': col_end
            })
            self.fillIndicatorRange(line_start, col_start, line_end, col_end,
                                    indicators['id'])

    def selection_change_listener(self):
        """
        Runs every time the text selection changes. This could get triggered
        multiple times while the mouse click is down, even if selection has not
        changed in itself.
        If there is a new selection is passes control to
        highlight_selected_matches.
        """
        # Get the current selection, exit if it has not changed
        line_from, index_from, line_to, index_to = self.getSelection()
        if self.previous_selection['col_end'] != index_to or \
                self.previous_selection['col_start'] != index_from or \
                self.previous_selection['line_start'] != line_from or \
                self.previous_selection['line_end'] != line_to:
            self.previous_selection['line_start'] = line_from
            self.previous_selection['col_start'] = index_from
            self.previous_selection['line_end'] = line_to
            self.previous_selection['col_end'] = index_to
            # Highlight matches
            self.reset_search_indicators()
            self.highlight_selected_matches()
 def setQssAutocomplete(self):
     api = QsciAPIs(self)
     widgets = ("QAbstractScrollArea", "QCheckBox", "QColumnView",
                "QComboBox", "QDateEdit", "QDateTimeEdit", "QDialog",
                "QDialogButtonBox", "QDockWidget", "QDoubleSpinBox",
                "QFrame", "QGroupBox", "QHeaderView", "QLabel", "QLineEdit",
                "QListView", "QListWidget", "QMainWindow", "QMenu",
                "QMenuBar", "QMessageBox", "QProgressBar", "QPushButton",
                "QRadioButton", "QScrollBar", "QSizeGrip", "QSlider",
                "QSpinBox", "QSplitter", "QStatusBar", "QTabBar",
                "QTabWidget", "QTableView", "QTableWidget", "QTextEdit",
                "QTimeEdit", "QToolBar", "QToolButton", "QToolBox",
                "QToolTip", "QTreeView", "QTreeWidget", "QWidget")
     properties = (
         "alternate-background-color", "background", "background-color",
         "background-image", "background-repeat", "background-position",
         "background-attachment", "background-clip", "background-origin",
         "border", "border-top", "border-right", "border-bottom",
         "border-left", "border-color", "border-top-color",
         "border-right-color", "border-bottom-color", "border-left-color",
         "border-image", "border-radius", "border-top-left-radius",
         "border-top-right-radius", "border-bottom-right-radius",
         "border-bottom-left-radius", "border-style", "border-top-style",
         "border-right-style", "border-bottom-style", "border-left-style",
         "border-width", "border-top-width", "border-right-width",
         "border-bottom-width", "border-left-width", "bottom",
         "button-layout", "color", "dialogbuttonbox-buttons-have-icons",
         "font", "font-family", "font-size", "font-style", "font-weight",
         "gridline-color", "height", "icon-size", "image", "image-position",
         "left", "lineedit-password-character",
         "lineedit-password-mask-delay", "margin", "margin-top",
         "margin-right", "margin-bottom", "margin-left", "max-height",
         "max-width", "messagebox-text-interaction-flags", "min-height",
         "min-width", "opacity*", "outline", "outline-color",
         "outline-offset", "outline-style", "outline-radius",
         "outline-bottom-left-radius", "outline-bottom-right-radius",
         "outline-top-left-radius", "outline-top-right-radius", "padding",
         "padding-top", "padding-right", "padding-bottom", "padding-left",
         "paint-alternating-row-colors-for-empty-area", "position", "right",
         "selection-background-color", "selection-color",
         "show-decoration-selected", "spacing", "subcontrol-origin",
         "subcontrol-position", "titlebar-show-tooltips-on-buttons",
         "widget-animation-duration", "text-align", "text-decoration",
         "top", "width")
     subcontrols0 = ("::add-line", "::add-page", "::branch", "::chunk",
                     "::close-button", "::corner", "::down-arrow",
                     "::down-button", "::drop-down", "::float-button",
                     "::groove", "::indicator", "::handle", "::icon",
                     "::item", "::left-arrow", "::left-corner",
                     "::menu-arrow", "::menu-button", "::menu-indicator",
                     "::right-arrow", "::pane", "::right-corner",
                     "::scroller", "::section", "::separator", "::sub-line",
                     "::sub-page", "::tab", "::tab-bar", "::tear",
                     "::tearoff", "::text", "::title", "::up-arrow",
                     "::up-button")
     pseudostates0 = (":active", ":adjoins-item", ":alternate", ":bottom",
                      ":checked", ":closable", ":closed", ":default",
                      ":disabled", ":editable", ":edit-focus", ":enabled",
                      ":exclusive", ":first", ":flat", ":floatable",
                      ":focus", ":has-children", ":has-siblings",
                      ":horizontal", ":hover", ":indeterminate", ":last",
                      ":left", ":maximized", ":middle", ":minimized",
                      ":movable", ":no-frame", ":non-exclusive", ":off",
                      ":on", ":only-one", ":open", ":next-selected",
                      ":pressed", ":previous-selected", ":read-only",
                      ":right", ":selected", ":top", ":unchecked",
                      ":vertical", ":window")
     subcontrols = ("add-line", "add-page", "branch", "chunk",
                    "close-button", "corner", "down-arrow", "down-button",
                    "drop-down", "float-button", "groove", "indicator",
                    "handle", "icon", "item", "left-arrow", "left-corner",
                    "menu-arrow", "menu-button", "menu-indicator",
                    "right-arrow", "pane", "right-corner", "scroller",
                    "section", "separator", "sub-line", "sub-page", "tab",
                    "tab-bar", "tear", "tearoff", "text", "title",
                    "up-arrow", "up-button")
     pseudostates = ("active", "adjoins-item", "alternate", "bottom",
                     "checked", "closable", "closed", "default", "disabled",
                     "editable", "edit-focus", "enabled", "exclusive",
                     "first", "flat", "floatable", "focus", "has-children",
                     "has-siblings", "horizontal", "hover", "indeterminate",
                     "last", "left", "maximized", "middle", "minimized",
                     "movable", "no-frame", "non-exclusive", "off", "on",
                     "only-one", "open", "next-selected", "pressed",
                     "previous-selected", "read-only", "right", "selected",
                     "top", "unchecked", "vertical", "window")
     kwset = (widgets, properties, subcontrols, pseudostates)
     for ks in kwset:
         for k in ks:
             api.add(k)
     api.prepare()
Example #35
0
    def updateAutoComplete(self, text=None):
        self.autocomplete = QsciAPIs(self.lexer)  # clear all

        self.keywords = self.lexer.keywords(1)
        self.keywords = self.keywords.split(' ')

        for word in self.keywords:
            self.autocomplete.add(word)

        self.autocomplete.add('super')
        self.autocomplete.add('self')
        self.autocomplete.add('__name__')
        self.autocomplete.add('__main__')
        self.autocomplete.add('__init__')
        self.autocomplete.add('__str__')
        self.autocomplete.add('__repr__')

        if not text:

            firstList = []  # list to edit
            secondList = []  # collect all items for autocomplete

            text = self.text()

            # parse complete text ....
            firstList = text.splitlines()

            for line in firstList:
                if 'def' in line:
                    item = line.strip()
                    item = item.strip('def')
                    item = item.replace(':', '')
                    if not item in secondList:
                        secondList.append(item)
                elif 'class' in line:
                    item = line.strip()
                    item = item.strip('class')
                    item = item.replace(':', '')
                    if not item in secondList:
                        secondList.append(item)


            text = text.replace('"', " ").replace("'", " ").replace("(", " ").replace\
                                (")", " ").replace("[", " ").replace("]", " ").replace\
                                (':', " ").replace(',', " ").replace("<", " ").replace\
                                (">", " ").replace("/", " ").replace("=", " ").replace\
                                (";", " ")

            firstList = text.split('\n')

            for row in firstList:

                if (row.strip().startswith('#')) or (
                        row.strip().startswith('//')):
                    continue

                else:
                    wordList = row.split()

                    for word in wordList:

                        if re.match("(^[0-9])", word):
                            continue

                        elif '#' in word or '//' in word:
                            continue

                        elif word in self.keywords:
                            continue

                        elif (word == '__init__') or (word == '__main__') or \
                             (word == '__name__') or (word == '__str__') or \
                             (word == '__repr__'):
                            continue

                        elif word in secondList:
                            continue

                        elif len(word) > 15:
                            continue

                        elif not len(word) < 3:
                            w = re.sub("{}<>;,:]", '', word)
                            #print(w)
                            secondList.append(w)

            # delete doubled entries
            x = set(secondList)
            secondList = list(x)

            # debugging ...
            #print(secondList)

            for item in secondList:
                self.autocomplete.add(item)

            self.autocomplete.prepare()
Example #36
0
class Editor(QsciScintilla):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.fileName = None
        self.parent = parent
        self.debugging = False
        self.line = None
        self.column = None

        self.wordlist = []
        self.searchtext = None

        self.font = QFont()
        self.font.setFamily("Inconsolata")
        self.pointSize = editor["pointSize"]
        self.font.setPointSize(self.pointSize)
        self.dialog = MessageBox(self)
        self.verticalScrollBar().setStyleSheet("""
            background-color: transparent;
            """)

        self.horizontalScrollBar().setStyleSheet("""
            background-color: transparent;
            """)

        self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setCaretForegroundColor(QColor("#FFFFFF"))
        self.setEdgeColumn(121)
        self.setEdgeMode(1)
        self.setEdgeColor(QColor("#8c8c8c"))
        self.setFont(self.font)
        self.setMarginSensitivity(1, True)
        self.markerDefine(QsciScintilla.RightArrow, 8)
        self.setMarkerBackgroundColor(QColor('#FF0000'), 8)
        self.indicator_number = 0
        self.indicator_value = 222
        self.indicator_color = QColor("#FF0000")
        self.draw_under_text = True
        # Initializing some stuff
        self.set_brace_colors(QColor("#98b4f9"), QColor("#edf40e"),
                              QColor("#98b4f9"), QColor("red"))

        self.cursorPositionChanged.connect(self.change_col)
        self.textChanged.connect(self.check_lines)

        self.set_linenumbers(QFontMetrics(self.font))
        self.setFoldMarginColors(QColor("#212121"), QColor("#212121"))
        self.set_indentation_settings()

    def set_up_tooltips(self):
        self.setCallTipsStyle(QsciScintilla.CallTipsNoContext)
        self.setCallTipsVisible(0)

        self.setCallTipsPosition(QsciScintilla.CallTipsAboveText)
        self.setCallTipsBackgroundColor(QColor("#FF0000"))

        self.setCallTipsForegroundColor(QColor("#FF0000"))
        self.setCallTipsHighlightColor(QColor("#FF0000"))

    def set_brace_colors(self,
                         matched_B=None,
                         matched_F=None,
                         unmatched_B=None,
                         unmatched_F=None):

        self.setMatchedBraceBackgroundColor(matched_B)
        self.setMatchedBraceForegroundColor(matched_F)
        self.setUnmatchedBraceBackgroundColor(unmatched_B)
        self.setUnmatchedBraceForegroundColor(unmatched_F)

        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

    def set_linenumbers(self, fontmetrics):
        self.setMarginsFont(self.font)

        self.setMarginWidth(0, fontmetrics.width("00000"))
        self.setMarginLineNumbers(0, True)

        self.setMarginsBackgroundColor(QColor("#212121"))
        self.setMarginsForegroundColor(QColor("#FFFFFF"))

    def set_indentation_settings(self):
        self.setIndentationsUseTabs(False)
        self.setTabWidth(4)

        self.SendScintilla(QsciScintilla.SCI_SETUSETABS, False)
        self.setAutoIndent(True)

        self.setTabIndents(True)

    def check_lines(self):
        line_n = self.lines()
        for i in range(line_n):
            if self.lineLength(i) > 121:
                # TODO: Make a character format or something
                pass
                #  print("Line over 121 characters on line", str(i+1))
                # self.setCursorPosition(i, 120)

    def python_highlighter(self):
        self.lexer = PythonLexer()
        self.lexer.setFoldComments(True)
        self.setCaretLineVisible(True)

        self.setDefaultSettings(self.lexer)
        self.setPythonAutocomplete()
        self.setFold()

    def json_highlighter(self):
        lexer = QsciLexerJSON()
        self.setDefaultSettings(lexer)

    def c_highlighter(self):
        lexer = QsciLexerCPP()
        self.setDefaultSettings(lexer)

    def xml_highlighter(self):
        lexer = QsciLexerXML()
        self.setDefaultSettings(lexer)

    def html_highlighter(self):
        lexer = QsciLexerHTML()
        self.setDefaultSettings(lexer)

    def setDefaultSettings(self, lexer):
        # self.setAutoIndent(True)
        lexer.setFont(self.font)

        lexer.setColor(QColor('white'), 0)  # default
        lexer.setColor(QColor('#6B6E6C'), PythonLexer.Comment)  # = 1
        lexer.setColor(QColor('#ADD4FF'), 2)  # Number = 2
        lexer.setColor(QColor('#38ef7d'), 3)  # DoubleQuotedString
        lexer.setColor(QColor('#38ef7d'), 4)  # SingleQuotedString
        lexer.setColor(QColor('#F6DC74'), 5)  # Keyword
        lexer.setColor(QColor('#38ef7d'), 6)  # TripleSingleQuotedString
        lexer.setColor(QColor('#38ef7d'), 7)  # TripleDoubleQuotedString
        lexer.setColor(QColor('#74F6C3'), 8)  # ClassName
        lexer.setColor(QColor('#FF6666'), 9)  # FunctionMethodName
        lexer.setColor(QColor('magenta'), 10)  # Operator
        lexer.setColor(QColor('white'), 11)  # Identifier
        lexer.setColor(QColor('gray'), 12)  # CommentBlock
        lexer.setColor(QColor('#a8ff78'), 13)  # UnclosedString
        lexer.setColor(QColor('gray'), 14)  # HighlightedIdentifier
        lexer.setColor(QColor('#FF00E7'), 15)  # Decorator

        lexer.setFont(QFont("Iosevka", weight=QFont.Bold), 5)
        self.setCaretLineBackgroundColor(QColor("#3C3B3F"))
        self.setLexer(lexer)

    def setPythonAutocomplete(self):
        self.autocomplete = QsciAPIs(self.lexer)
        self.keywords = wordList

        for word in self.keywords:
            self.autocomplete.add(word)

        self.setAutoCompletionThreshold(2)

        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
        self.updateAutoComplete(self.parent.fileName)

        self.autocomplete.prepare()

    def setFold(self):
        # setup Fold Styles for classes and functions ...
        x = self.FoldStyle(self.FoldStyle(5))
        # self.textPad.folding()
        if not x:
            self.foldAll(False)

        self.setFolding(x)
        # self.textPad.folding()

    def unsetFold(self):
        self.setFolding(0)

    def updateAutoComplete(self, file_path=None):

        for i in tokenize(file_path):
            for j in i:
                if j not in self.wordlist:
                    self.wordlist.append(j)
        for word in self.wordlist:
            self.autocomplete.add(word)

        self.autocomplete.prepare()

    def change_col(self, line,
                   column):  # Responsible for changing the column bar.
        self.line = line
        self.column = column

    def check_if_func(self, word):  # Checks if a word is a built in function
        word_array = list(word)
        for wo in word_array:
            if wo in ["{", "}", "'", '"', "[", "]", "(", ")"]:
                word_array.remove(wo)
        for w in funcList:
            if w == "".join(word_array):
                return True

    def check_if_error(self, word):
        if word in errorList:  # This is the list where all possible errors are defined
            return True

    def keyReleaseEvent(self, e):
        if e.key() == Qt.Key_Return:
            try:
                self.updateAutoComplete(self.parent.fileName)
            except AttributeError as E:
                print(E, "on line 210 in TextEditor.py")

        if e.key() == Qt.Key_Backspace:
            pass

    def mousePressEvent(self, e):
        super().mousePressEvent(e)

        if QGuiApplication.queryKeyboardModifiers() == Qt.ControlModifier:
            word = self.wordAtLineIndex(self.getCursorPosition()[0],
                                        self.getCursorPosition()[1])
            print(word)
            if self.check_if_func(word):
                url = "https://docs.python.org/3/library/functions.html#" + word
                self.parent.parent.openBrowser(
                    url, word)  # Runs the openBrowser function in Main class
            elif self.check_if_error(word):
                url = "https://docs.python.org/3/library/exceptions.html#" + word
                print(url)

                self.parent.parent.openBrowser(url, word)

    def keyPressEvent(self, e):
        if e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_F:
            text, okPressed = QInputDialog.getText(self, 'Find', 'Find what: ')

            self.setSelectionBackgroundColor(QColor("#6be585"))

            if okPressed:
                if text == "":
                    text = " "
                    self.dialog.noMatch(text)

                self.searchtext = text
                """
                This is the way to implement a search function using QScintilla 
                http://pyqt.sourceforge.net/Docs/QScintilla2/classQsciScintilla.html#a37ac2bea94eafcfa639173557a821200
                """

                if self.findFirst(self.searchtext, False, True, False, True,
                                  True, -1, -1, True, False):
                    pass
                else:
                    self.dialog.noMatch(self.searchtext)

        if e.key() == Qt.Key_F3:
            self.findNext()
            self.setSelectionBackgroundColor(QColor("#6be585"))

        if e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_L:
            self.setCursorPosition(self.line, self.column + 1)
            return
        if e.modifiers() == Qt.ControlModifier and e.key() == 77:

            self.setCursorPosition(self.line + 1, self.column)
            return
        if e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_J:
            self.setCursorPosition(self.line, self.column - 1)

        if e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_I:
            self.setCursorPosition(self.line - 1, self.column)

        if e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_T:
            self.parent.parent.realterminal()
            return

        super().keyPressEvent(e)
Example #37
0
class CodeEditor(QsciScintilla):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.filename = None
        self.fileBrowser = None
        self.mainWindow = parent
        self.debugging = False

        c = Configuration()
        self.pointSize = int(c.getFont())
        self.tabWidth = int(c.getTab())

        # Scrollbars
        self.verticalScrollBar().setStyleSheet("""border: 20px solid black;
            background-color: darkgreen;
            alternate-background-color: #FFFFFF;""")

        self.horizontalScrollBar().setStyleSheet("""border: 20px solid black;
            background-color: darkgreen;
            alternate-background-color: #FFFFFF;""")

        # matched / unmatched brace color ...
        self.setMatchedBraceBackgroundColor(QColor('#000000'))
        self.setMatchedBraceForegroundColor(QColor('cyan'))
        self.setUnmatchedBraceBackgroundColor(QColor('#000000'))
        self.setUnmatchedBraceForegroundColor(QColor('red'))

        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # edge mode ... line at 79 characters
        self.setEdgeColumn(79)
        self.setEdgeMode(1)
        self.setEdgeColor(QColor('dark green'))

        # Set the default font
        self.font = QFont()
        self.font.setFamily(c.getFontFamily())
        self.font.setFixedPitch(True)
        self.font.setPointSize(self.pointSize)
        self.setFont(self.font)
        self.setMarginsFont(self.font)

        # Margin 0 is used for line numbers
        fontmetrics = QFontMetrics(self.font)
        self.setMarginsFont(self.font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 5)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QColor("#000000"))
        self.setMarginsForegroundColor(QColor("#FFFFFF"))

        # Margin 1 for breakpoints
        self.setMarginType(1, QsciScintilla.SymbolMargin)
        self.setMarginWidth(1, "00")
        sym = QsciScintilla.Circle

        self.markerDefine(sym, 1)

        self.setMarginMarkerMask(0, 0b1111)

        handle_01 = self.markerAdd(1, 0)

        # FoldingBox
        self.setFoldMarginColors(QColor('dark green'), QColor('dark green'))

        # CallTipBox
        self.setCallTipsForegroundColor(QColor('#FFFFFF'))
        self.setCallTipsBackgroundColor(QColor('#282828'))
        self.setCallTipsHighlightColor(QColor('#3b5784'))
        self.setCallTipsStyle(QsciScintilla.CallTipsContext)
        self.setCallTipsPosition(QsciScintilla.CallTipsBelowText)
        self.setCallTipsVisible(-1)

        # change caret's color
        self.SendScintilla(QsciScintilla.SCI_SETCARETFORE, QColor('#98fb98'))
        self.setCaretWidth(4)

        # tab Width
        self.setIndentationsUseTabs(False)
        self.setTabWidth(self.tabWidth)
        # use Whitespaces instead tabs
        self.SendScintilla(QsciScintilla.SCI_SETUSETABS, False)
        self.setAutoIndent(True)
        self.setTabIndents(True)

        # BackTab
        self.setBackspaceUnindents(True)

        # Current line visible with special background color or not :)
        #self.setCaretLineVisible(False)
        #self.setCaretLineVisible(True)
        #self.setCaretLineBackgroundColor(QColor("#020202"))

        # not too small
        self.setMinimumSize(300, 300)

        # get style
        self.style = None

        # Call the Color-Function: ...
        self.setPythonStyle()

        #self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)

        # Contextmenu
        self.setContextMenuPolicy(Qt.ActionsContextMenu)
        undoAction = QAction("Undo", self)
        undoAction.triggered.connect(self.undoContext)
        redoAction = QAction("Redo", self)
        redoAction.triggered.connect(self.redoContext)
        sepAction1 = QAction("", self)
        sepAction1.setSeparator(True)
        cutAction = QAction("Cut", self)
        cutAction.triggered.connect(self.cutContext)
        copyAction = QAction("Copy", self)
        copyAction.triggered.connect(self.copyContext)
        pasteAction = QAction("Paste", self)
        pasteAction.triggered.connect(self.pasteContext)
        sepAction2 = QAction("", self)
        sepAction2.setSeparator(True)
        sepAction3 = QAction("", self)
        sepAction3.setSeparator(True)
        selectAllAction = QAction("Select All", self)
        selectAllAction.triggered.connect(self.getContext)
        terminalAction = QAction("Open Terminal", self)
        terminalAction.triggered.connect(self.termContext)

        self.addAction(undoAction)
        self.addAction(redoAction)
        self.addAction(sepAction1)
        self.addAction(cutAction)
        self.addAction(copyAction)
        self.addAction(pasteAction)
        self.addAction(sepAction2)
        self.addAction(selectAllAction)
        self.addAction(sepAction3)
        self.addAction(terminalAction)

        # signals
        self.SCN_FOCUSIN.connect(self.onFocusIn)
        self.textChanged.connect(self.onTextChanged)
        self.marginClicked.connect(self.onMarginClicked)

    def onFocusIn(self):
        self.mainWindow.refresh(self)

    def onTextChanged(self):
        notebook = self.mainWindow.notebook
        textPad = notebook.currentWidget()
        index = notebook.currentIndex()

        if self.debugging is True:
            self.mainWindow.statusBar.showMessage(
                'remember to update CodeView if you delete or change lines in CodeEditor !',
                3000)

        if textPad == None:
            return

        if textPad.filename:
            if not '*' in notebook.tabText(index):
                fname = os.path.basename(textPad.filename)
                fname += '*'
                notebook.setTabText(index, fname)

        else:
            fname = notebook.tabText(index)
            fname += '*'

            if not '*' in notebook.tabText(index):
                notebook.setTabText(index, fname)

    def onMarginClicked(self):
        pass

    def checkPath(self, path):
        if '\\' in path:
            path = path.replace('\\', '/')
        return path

    def undoContext(self):
        self.undo()

    def redoContext(self):
        self.redo()

    def cutContext(self):
        self.cut()

    def copyContext(self):
        self.copy()

    def pasteContext(self):
        self.paste()

    def getContext(self):
        self.selectAll()

    def termContext(self):
        c = Configuration()
        system = c.getSystem()
        command = c.getTerminal(system)

        thread = RunThread(command)
        thread.start()

    def getLexer(self):
        return self.lexer

    def setPythonStyle(self):
        self.style = 'Python'

        # Set Python lexer
        self.setAutoIndent(True)

        #self.lexer = QsciLexerPython()
        self.lexer = PythonLexer()
        self.lexer.setFont(self.font)
        self.lexer.setFoldComments(True)

        # set Lexer
        self.setLexer(self.lexer)

        self.setCaretLineBackgroundColor(QColor("#344c4c"))
        self.lexer.setDefaultPaper(QColor("black"))
        self.lexer.setDefaultColor(QColor("white"))
        self.lexer.setColor(QColor('white'), 0)  # default
        self.lexer.setPaper(QColor('black'), -1)  # default -1 vor all styles
        self.lexer.setColor(QColor('gray'), PythonLexer.Comment)  # = 1
        self.lexer.setColor(QColor('orange'), 2)  # Number = 2
        self.lexer.setColor(QColor('lightblue'), 3)  # DoubleQuotedString
        self.lexer.setColor(QColor('lightblue'), 4)  # SingleQuotedString
        self.lexer.setColor(QColor('#33cccc'), 5)  # Keyword
        self.lexer.setColor(QColor('lightblue'), 6)  # TripleSingleQuotedString
        self.lexer.setColor(QColor('lightblue'), 7)  # TripleDoubleQuotedString
        self.lexer.setColor(QColor('#ffff00'), 8)  # ClassName
        self.lexer.setColor(QColor('#ffff66'), 9)  # FunctionMethodName
        self.lexer.setColor(QColor('magenta'), 10)  # Operator
        self.lexer.setColor(QColor('white'), 11)  # Identifier
        self.lexer.setColor(QColor('gray'), 12)  # CommentBlock
        self.lexer.setColor(QColor('#ff471a'), 13)  # UnclosedString
        self.lexer.setColor(QColor('gray'), 14)  # HighlightedIdentifier
        self.lexer.setColor(QColor('#5DD3AF'), 15)  # Decorator
        self.setPythonAutocomplete()
        self.setFold()

    def setPythonAutocomplete(self):

        self.autocomplete = QsciAPIs(self.lexer)
        self.keywords = self.lexer.keywords(1)

        self.keywords = self.keywords.split(' ')

        for word in self.keywords:
            self.autocomplete.add(word)

        self.autocomplete.add('super')
        self.autocomplete.add('self')
        self.autocomplete.add('__name__')
        self.autocomplete.add('__main__')
        self.autocomplete.add('__init__')
        self.autocomplete.add('__str__')
        self.autocomplete.add('__repr__')

        self.autocomplete.prepare()

        ## Set the length of the string before the editor tries to autocomplete
        self.setAutoCompletionThreshold(3)

        ## Tell the editor we are using a QsciAPI for the autocompletion
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)

        self.updateAutoComplete()

    def setFold(self):
        # setup Fold Styles for classes and functions ...
        x = self.FoldStyle(self.FoldStyle(5))
        #self.textPad.folding()
        if not x:
            self.foldAll(False)

        self.setFolding(x)
        #self.textPad.folding()

    def unsetFold(self):
        self.setFolding(0)

    def keyReleaseEvent(self, e):
        # feed the autocomplete with the words from editor
        # simple algorithm to do this ... everytime after Enter

        # refresh CodeView
        text = self.text()
        self.updateCodeView(text)

        # if ENTER was hit ... :

        if e.key() == Qt.Key_Return:

            self.updateAutoComplete()

    def updateCodeView(self, text=''):
        codeView = self.mainWindow.codeView
        codeViewDict = codeView.makeDictForCodeView(text)
        codeView.updateCodeView(codeViewDict)

    def updateAutoComplete(self, text=None):

        if not text:

            firstList = []  # list to edit
            secondList = []  # collect all items for autocomplete

            text = self.text()

            # parse complete text ....
            firstList = text.splitlines()

            for line in firstList:
                if 'def' in line:
                    item = line.strip()
                    item = item.strip('def')
                    item = item.replace(':', '')
                    if not item in secondList:
                        secondList.append(item)
                elif 'class' in line:
                    item = line.strip()
                    item = item.strip('class')
                    item = item.replace(':', '')
                    if not item in secondList:
                        secondList.append(item)


            text = text.replace('"', " ").replace("'", " ").replace("(", " ").replace\
                                (")", " ").replace("[", " ").replace("]", " ").replace\
                                (':', " ").replace(',', " ").replace("<", " ").replace\
                                (">", " ").replace("/", " ").replace("=", " ").replace\
                                (";", " ")

            firstList = text.split('\n')

            for row in firstList:

                if (row.strip().startswith('#')) or (
                        row.strip().startswith('//')):
                    continue

                else:
                    wordList = row.split()

                    for word in wordList:

                        if re.match("(^[0-9])", word):
                            continue

                        elif '#' in word or '//' in word:
                            continue

                        elif word in self.keywords:
                            continue

                        elif (word == '__init__') or (word == '__main__') or \
                             (word == '__name__') or (word == '__str__') or \
                             (word == '__repr__'):
                            continue

                        elif word in secondList:
                            continue

                        elif len(word) > 15:
                            continue

                        elif not len(word) < 3:
                            w = re.sub("{}<>;,:]", '', word)
                            #print(w)
                            secondList.append(w)

            # delete doubled entries
            x = set(secondList)
            secondList = list(x)

            # debugging ...
            #print(secondList)

            for item in secondList:
                self.autocomplete.add(item)

            self.autocomplete.prepare()

    def setPythonPrintStyle(self):
        # Set None lexer
        self.font = QFont()
        self.font.setFamily(c.getFontFamily())
        self.font.setFixedPitch(True)
        self.font.setPointSize(10)
        self.setFont(self.font)

        self.lexer = PythonLexer()
        self.lexer.setFont(self.font)
        # set Lexer
        self.setLexer(self.lexer)

        self.setCaretLineBackgroundColor(QColor("#344c4c"))
        self.lexer.setDefaultPaper(QColor("white"))
        self.lexer.setDefaultColor(QColor("black"))
        self.lexer.setColor(QColor('black'), -1)  # default
        self.lexer.setPaper(QColor('white'), -1)  # default
        self.lexer.setColor(QColor('gray'),
                            PythonLexer.Comment)  # entspricht 1
        self.lexer.setColor(QColor('orange'), 2)  # Number entspricht 2
        self.lexer.setColor(QColor('darkgreen'),
                            3)  # DoubleQuotedString entspricht 3
        self.lexer.setColor(QColor('darkgreen'),
                            4)  # SingleQuotedString entspricht 4
        self.lexer.setColor(QColor('darkblue'), 5)  # Keyword entspricht 5
        self.lexer.setColor(QColor('darkgreen'),
                            6)  # TripleSingleQuotedString entspricht 6
        self.lexer.setColor(QColor('darkgreen'),
                            7)  # TripleDoubleQuotedString entspricht 7
        self.lexer.setColor(QColor('red'), 8)  # ClassName entspricht 8
        self.lexer.setColor(QColor('crimson'),
                            9)  # FunctionMethodName entspricht 9
        self.lexer.setColor(QColor('green'), 10)  # Operator entspricht 10
        self.lexer.setColor(QColor('black'),
                            11)  # Identifier entspricht 11 ### alle Wörter
        self.lexer.setColor(QColor('gray'), 12)  # CommentBlock entspricht 12
        self.lexer.setColor(QColor('#ff471a'),
                            13)  # UnclosedString entspricht 13
        self.lexer.setColor(QColor('gray'),
                            14)  # HighlightedIdentifier entspricht 14
        self.lexer.setColor(QColor('#5DD3AF'), 15)  # Decorator entspricht 15

        self.setNoneAutocomplete()
        self.unsetFold()

        self.font = QFont()
        self.font.setFamily('Mono')
        self.font.setFixedPitch(True)
        self.font.setPointSize(self.pointSize)

    def setNoneAutocomplete(self):
        #AutoCompletion
        self.autocomplete = Qsci.QsciAPIs(self.lexer)
        self.autocomplete.clear()

        self.autocomplete.prepare()

        self.setAutoCompletionThreshold(3)
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)

    def resetPythonPrintStyle(self, lexer):

        self.font = QFont()
        self.font.setFamily(c.getFontFamily())
        self.font.setFixedPitch(True)
        self.font.setPointSize(self.pointSize)
        self.setFont(self.font)

        lexer.setFont(self.font)
        # set Lexer
        self.setLexer(lexer)

        # margins reset

        # Margin 0 is used for line numbers
        fontmetrics = QFontMetrics(self.font)
        self.setMarginsFont(self.font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 5)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QColor("#000000"))
        self.setMarginsForegroundColor(QColor("#FFFFFF"))

        # FoldingBox
        self.setFoldMarginColors(QColor('dark green'), QColor('dark green'))
class ControlCodeEditor(ControlBase):
    """
	Control that offers a code editor with pretty-print and line numbers and a save button
	"""

    ARROW_MARKER_NUM = 8

    def __init__(self, *args, **kwargs):
        """
		
		:param label: 
		:param default: 
		:param helptext: 
		"""
        self._read_only = kwargs.get('readonly', False)
        self._changed_func = None
        super(ControlCodeEditor, self).__init__(*args, **kwargs)

        self.discart_event = kwargs.get('discart_event', self.discart_event)

    def init_form(self):
        """
		
		"""

        control_path = tools.getFileInSameDirectory(__file__, "code_editor.ui")
        self._form = uic.loadUi(control_path)

        self._code_editor = self._form.code_editor
        self._save_button = self._form.save_button
        self._discart_button = self._form.discart_button

        self._save_button.clicked[bool].connect(self.on_save_changes)
        self._discart_button.clicked[bool].connect(self.on_discart_changes)

        if self._read_only:
            self._code_editor.setReadOnly(True)
            self._save_button.setVisible(False)
            self._discart_button.setVisible(False)

        self.form.font_size.addItem('9')
        self.form.font_size.addItem('10')
        self.form.font_size.addItem('11')
        self.form.font_size.addItem('12')
        self.form.font_size.addItem('14')
        self.form.font_size.addItem('18')
        self.form.font_size.addItem('24')

        # Set the default font size
        index = self.form.font_size.findText(
            conf.PYFORMS_CONTROL_CODE_EDITOR_DEFAULT_FONT_SIZE)
        self.form.font_size.setCurrentIndex(index)

        self.form.font_size.currentIndexChanged.connect(
            self.__font_size_index_changed)

        self.form.save_button.setIcon(QIcon(conf.PYFORMS_ICON_CODEEDITOR_SAVE))
        self.form.discart_button.setIcon(
            QIcon(conf.PYFORMS_ICON_CODEEDITOR_DISCART))

        self.lexer = QsciLexerPython

        self._code_editor.keyPressEvent = self._key_pressed
        self._changed_func = None

        self.value = self._value
        super(ControlCodeEditor, self).init_form()

    def __font_size_index_changed(self, index):
        item = self.form.font_size.currentText()
        if len(item) >= 1:
            self._load_code_editor_settings()

    def _load_code_editor_settings(self):
        """
		Load settings on the code editor like, font style, margins, scroll, etc.
		Based on the example from http://eli.thegreenplace.net/2011/04/01/sample-using-qscintilla-with-pyqt/
		"""

        item = self.form.font_size.currentText()
        size = int(item)

        # Set the default font
        font = QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(size)
        self._code_editor.setFont(font)
        self._code_editor.setMarginsFont(font)

        # Margin 0 is used for line numbers
        fontmetrics = QFontMetrics(font)
        self._code_editor.setMarginsFont(font)
        self._code_editor.setMarginWidth(0, fontmetrics.width("00000") + 6)
        self._code_editor.setMarginLineNumbers(0, True)
        self._code_editor.setMarginsBackgroundColor(QColor("#cccccc"))

        # Clickable margin 1 for showing markers
        self._code_editor.setMarginSensitivity(1, True)
        self._code_editor.marginClicked.connect(self.on_margin_clicked)
        self._code_editor.markerDefine(QsciScintilla.RightArrow,
                                       self.ARROW_MARKER_NUM)
        self._code_editor.setMarkerBackgroundColor(QColor("#ee1111"),
                                                   self.ARROW_MARKER_NUM)

        # Detect changes to text
        self._code_editor.modificationChanged.connect(
            self.on_modification_changed)

        # Brace matching: enable for a brace immediately before or after the current position
        self._code_editor.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Current line visible with special background color
        self._code_editor.setCaretLineVisible(True)
        self._code_editor.setCaretLineBackgroundColor(QColor("#ffe4e4"))

        # Set Python lexer
        # Set style for Python comments (style number 1) to a fixed-width Courier.
        lexer = self.lexer()
        lexer.setDefaultFont(font)
        self._code_editor.setLexer(lexer)
        self._code_editor.setIndentationWidth(4)
        # self._code_editor.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')
        self._code_editor.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1)

        # Don't want to see the horizontal scrollbar at all
        # Use raw message to Scintilla here (all messages are documented here: http://www.scintilla.org/ScintillaDoc.html)
        self._code_editor.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)

        self._lexer_obj = lexer
        self.qsci_api = QsciAPIs(self._lexer_obj)
        ## Add autocompletion strings
        self.qsci_api.add("aLongString")
        self.qsci_api.add("aLongerString")
        self.qsci_api.add("aDifferentString")
        self.qsci_api.add("sOmethingElse")
        ## Compile the api for use in the lexer
        self.qsci_api.prepare()
        self._code_editor.setAutoCompletionThreshold(1)
        self._code_editor.setAutoCompletionSource(QsciScintilla.AcsAll)

    # not too small
    # self._code_editor.setMinimumSize(600, 450)

    ###################################################################
    ############ Events ###############################################
    ###################################################################

    def on_margin_clicked(self, nmargin, nline, modifiers):  # pylint: disable=unused-argument
        """
		On margin clicked, toggle marker for the line the margin was clicked on
		:param nmargin:
		:type nmargin:
		:param nline:
		:type nline:
		:param modifiers:
		:type modifiers:
		"""
        if self._code_editor.markersAtLine(nline) != 0:
            self._code_editor.markerDelete(nline, self.ARROW_MARKER_NUM)
        else:
            self._code_editor.markerAdd(nline, self.ARROW_MARKER_NUM)

    def on_modification_changed(self):
        """
		On modification change, re-enable save button
		"""
        if self._changed_func:
            self._save_button.setEnabled(True)
            self._discart_button.setEnabled(True)

    def on_save_changes(self):
        """
		On button save clicked, save changes made on the code editor to file
		"""
        if self.changed_event():
            self._code_editor.setModified(False)
            self._save_button.setEnabled(False)
            self._discart_button.setEnabled(False)

    def on_discart_changes(self):

        if self.discart_event():
            self._code_editor.setModified(False)
            self._save_button.setEnabled(False)
            self._discart_button.setEnabled(False)

    def discart_event(self):

        return True

    def _key_pressed(self, event):
        """
		Handle KeyPressed event
		We only care about CTRL-S in order to save changes
		:param event: key event
		"""
        QsciScintilla.keyPressEvent(self._code_editor, event)
        if event.key() in [QtCore.Qt.Key_S, QtCore.Qt.Key_Save]:
            modifiers = QApplication.keyboardModifiers()
            if modifiers == QtCore.Qt.ControlModifier and self.is_modified:
                logger.debug("Saving...")
                self.on_save_changes()

        self.key_pressed_event(event)

    def key_pressed_event(self, event):
        """
		Override KeyPressed event as you like
		:param event: key event
		"""
        pass

    @property
    def is_modified(self):
        return self._code_editor.isModified()

    ###################################################################
    ############ Properties ###########################################
    ###################################################################

    @property
    def lexer(self):
        return self._lexer

    @lexer.setter
    def lexer(self, value):
        self._lexer = value
        self._load_code_editor_settings()

    @property
    def value(self):
        return self._code_editor.text()

    @value.setter
    def value(self, value):
        if value is not None:
            self._code_editor.setText(str(value))
            self._code_editor.setModified(False)
            self._save_button.setEnabled(False)
            self._discart_button.setEnabled(False)

    @property
    def changed_event(self):
        return self._changed_func if self._changed_func else (lambda: 0)

    @changed_event.setter
    def changed_event(self, value):
        self._changed_func = value
Example #39
0
class CppEditor(QsciScintilla):
    '''
    classdocs
    '''
    def __init__(self, parent=None, fileName=None, readOnlyFiles=[]):
        '''
        Constructor
        '''
        super(CppEditor, self).__init__(parent)
        self.parent = parent
        self.roFiles = readOnlyFiles

        self.setAcceptDrops(False) # drag&drop is on its parent

        # Set the default font
        font = QtGui.QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.setMarginsFont(font)

        # C/C++ lexer
        self.lexer = QsciLexerCPP(self,  True)
        self.lexer.setDefaultFont(font)
        self.libraryAPIs = QsciAPIs(QsciLexerCPP(self,True))
        self.setLexer(self.lexer)
        #self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')

        # Auto-indent
        self.setTabWidth(4)
        #self.setIndentationsUseTabs(False)
        self.setAutoIndent(True)

        # Current line visible with special background color
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QtGui.QColor("#ffe4e4"))

        # Enable brace matching
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Enable folding visual- use boxes
        self.setFolding(QsciScintilla.BoxedTreeFoldStyle)

        # show line numbers
        fontmetrics = QtGui.QFontMetrics(font)
        self.setMarginsFont(font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 4)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QtGui.QColor("#ccccee"))

        # not too small
        self.setMinimumSize(400, 200)

        # set the length of the string before the editor tries to auto-complete
        self.setAutoCompletionThreshold(3)
        # tell the editor we are using a QsciAPI for the auto-completion
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
        # removed remaining right side characters from the current cursor
        self.setAutoCompletionReplaceWord(True)

        # "CTRL+Space" autocomplete
        self.shortcut_ctrl_space = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Space"), self)
        self.shortcut_ctrl_space.activated.connect(self.autoCompleteFromAll)

        if fileName:
            self.curFile = fileName
            self.loadFile(fileName)
            self.isUntitled = False
        else:
            self.curFile = PROJECT_NONAME + USER_CODE_EXT
            self.setText( __default_content__ )
            self.isUntitled = True

        self.updateApiKeywords()
        self.isModified = False
        self.textChanged.connect(self.onTextChanged )

    def onTextChanged(self):
        self.isModified = True
        self.parent.onChildContentChanged()

    def loadFile(self, fileName):
        try:
            self.clear()
            with open(fileName, 'r') as f:
                for line in f.readlines():
                    self.append(line)
            return True
        except:
            QtWidgets.QMessageBox.warning(self, PROJECT_ALIAS,
                    "failed to read %s." % fileName )
            return False


    def saveFile(self, fileName):
        if fileName.find(' ')>=0:
            QtWidgets.QMessageBox.warning(self, PROJECT_ALIAS,
                    'File path "%s" contains space(s). Please save to a valid location.'%fileName)
            return None

        try:
            with open(fileName, 'wt') as f:
                f.write(self.text())
        except:
            QtWidgets.QMessageBox.warning(self, PROJECT_ALIAS,
                    "Failed to save %s." % fileName )
            return None

        self.curFile = fileName
        self.isUntitled = False
        self.isModified = False
        return fileName

    def saveAs(self):
        fileName, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Save As",
                self.curFile, PROJECT_ALIAS + " (*" + USER_CODE_EXT + ");;" +
                    "C source (*.c);;C++ source (*.cpp);;Text File (*.txt);;All files (*.*)" )
        if not fileName:
            return None
        return self.saveFile(fileName)

    def save(self):
        f1 = os.path.abspath(self.curFile)
        for fname in self.roFiles:
            if f1 == os.path.abspath(fname): # same file
                if QtWidgets.QMessageBox.question(self.parent, "Project is read-only",
                        "This project is marked as \"read-only\".\n" + \
                        "Please click \"Cancel\" and save this to another location.\n\n" + \
                        "Continue saving the current project anyway?", "OK", "Cancel"):
                    return None
                #return self.saveAs()
                return self.saveFile(self.curFile)
        if self.isUntitled:
            return self.saveAs()
        else:
            return self.saveFile(self.curFile)

    def currentFile(self):
        return self.curFile

    def modified(self):
        return self.isModified

    def updateApiKeywords(self):
        self.libraryAPIs.clear()
        self.apiKeywords = self.parent.getDefaultKeywords()
        headerfiles = []
        for line in range(self.lines()):
            txt = str(self.text(line)).strip()
            if txt.find('int') == 0: # e.g. reached "int main()"
                break
            elif txt.find('#include') == 0:
                txt = ''.join(txt.split())
                temp = txt[len('#includes')-1 : ]
                header = temp[1:-1] # get the header file
                hfile = os.path.join('libraries', header[:-2], header)
                if os.path.isfile(hfile):
                    if not (hfile in headerfiles): headerfiles.append( hfile )
        if len( headerfiles ):
            #print 'parsing: ', headerfiles
            self.apiKeywords += getLibraryKeywords( headerfiles )
        #self.apiKeywords = list(set(self.apiKeywords)) # remove duplicates
        for keyword in self.apiKeywords:
            self.libraryAPIs.add( keyword )
        self.libraryAPIs.prepare()
        self.lexer.setAPIs(self.libraryAPIs)

    def insertIncludeDirective(self, library=''):
        directive =  '#include <' + library + '.h>\r\n'
        insert_pos = 0
        found_inc = False
        for line in range(self.lines()):
            txt = str(self.text(line)).strip()
            if txt.find('int') == 0: # e.g. reached "int main()"
                insert_pos = line - 1
                break
            elif txt.find('#include') == 0:
                found_inc = True
            elif found_inc:
                insert_pos = line
                break
        if insert_pos < 0 or insert_pos >= self.lines():
            insert_pos = 0
        self.insertAt(directive, insert_pos, 0)
        self.updateApiKeywords()