C语言代码审计项目——编辑器、高亮、查找替换、选词跳转(上)

战前准备

  • github以及它的访问权限
  • 强大的心理支撑
  • 一点点英文水平(至少会念ABC )
  • 会写测试样例
  • 面向对象要学好(没学真的会感到是在地狱)

使用环境

  • python 3.8.0
  • pyqt 5.15.9
  • pycharm 2021.3

基础类

基本上在这两个类反复横跳

  • QsciScintilla
  • QTextEdit

手册或者参考

QTextEdit的应用
这是我的小小爹
QsciScintilla的应用
这是大爹但又没完全是

QTextEdit

跟着github上的代码走就没问题…

import sys
from PyQt5.QtCore import QRegExp
from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter

## 不用QS的...
def format(color, style=''):
    """
    Return a QTextCharFormat with the given attributes.
    """
    _color = QColor()
    if type(color) is not str:
        _color.setRgb(color[0], color[1], color[2])
    else:
        _color.setNamedColor(color)

    _format = QTextCharFormat()
    _format.setForeground(_color)
    if 'bold' in style:
        _format.setFontWeight(QFont.Bold)
    if 'italic' in style:
        _format.setFontItalic(True)

    return _format


STYLES = {
    'header': format([0, 128, 0]),  # 头文件
    'd_header': format([60,179,113]),  # 头文件
    'keyword': format('#2E8B57', 'bold'),
    'operator': format([255, 140, 0]),  # 运算符
    'brace': format([255, 140, 0]),  # 符号
    'defclass': format([0, 80, 50], 'bold'),  # 类名
    'string': format([132, 26, 138]),  # 字符串
    'string2': format([132, 26, 138]),  # 字符串
    'comment': format([107, 147, 186]),  # 注释
    'self': format([150, 85, 140], 'italic'),  # 自身
    'numbers': format([42, 0, 255]),  # 数字
    'constant': format([202, 0, 202], 'bold'),  # 常量
    'deprecated': format([123, 23, 43], 'bold underline'),  # 弃用的成员
    'enums': format([128, 0, 255]),  # 枚举
    'fields': format([128, 0, 128]),  # 变量
    'return': format([255, 0, 85], 'bold'),  # return关键字
    'method_decl': format([255, 128, 64], 'bold'),  # 方法定义
    'method': format([0, 48, 96]),  # 方法
    'others': format([78, 123, 0]),  # 其他
    'static_fields': format([33, 0, 189], 'bold'),  # 静态变量
}


class CppHighlighter(QSyntaxHighlighter):
    """Syntax highlighter for the C/C++ language.
    """
    # header
    header = [
        'stdio.h', 'stdlib.h', 'math.h', 'string.h', 'time.h', 'ctype.h',
        'stdbool.h', 'assert.h', 'limits.h', 'float.h', 'stddef.h', 'errno.h',
        'signal.h', 'setjmp.h', 'stdarg.h', 'locale.h', 'wchar.h', 'time.h',
        'unistd.h', 'fcntl.h', 'sys/types.h', 'sys/stat.h', 'dirent.h',
        'pthread.h', 'semaphore.h', 'sys/socket.h', 'netinet/in.h',
        'arpa/inet.h', 'netdb.h', 'sys/time.h', 'sys/wait.h'
    ]
    # C/C++ keywords
    keywords = [
        'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do',
        'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'int',
        'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static',
        'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile',
        'while', 'bool', 'catch', 'class', 'const_cast', 'delete', 'dynamic_cast',
        'explicit', 'false', 'friend', 'inline', 'mutable', 'namespace', 'new',
        'operator', 'private', 'protected', 'public', 'reinterpret_cast',
        'static_cast', 'template', 'this', 'throw', 'true', 'try', 'typeid',
        'typename', 'using', 'virtual', 'wchar_t',
    ]

    # C/C++ operators
    operators = [
        '=',
        # Comparison
        '==', '!=', '<', '<=', '>', '>=',
        # Arithmetic
        '\+', '-', '\*', '/', '//', '\%', '\*\*',
        # In-place
        '\+=', '-=', '\*=', '/=', '\%=',
        # Bitwise
        '\^', '\|', '\&', '\~', '>>', '<<',
    ]
    # C/C++ braces
    braces = [
        '\{', '\}', '\(', '\)', '\[', '\]',
    ]
    def __init__(self, document):
        QSyntaxHighlighter.__init__(self, document)

        rules = []
        # Header rules: #include<XX.h>
        header_rules1 = [('#include\s*<{}>'.format(a), 0, STYLES['header'])
                         for a in CppHighlighter.header]
        # Header rules: #include "XXX.h"
        header_rules2 = [(r'#include\s*"([^"]+)"', 0, STYLES['d_header']),]

        rules += [(r'\b%s\b' % w, 0, STYLES['keyword'])
                  for w in CppHighlighter.keywords]
        # print(rules)
        rules += [(r'%s' % o, 0, STYLES['operator'])
                  for o in CppHighlighter.operators]
        rules += [(r'%s' % b, 0, STYLES['brace'])
                  for b in CppHighlighter.braces]
        # All other rules
        rules += [
            # 'self'
            (r'\bself\b', 0, STYLES['self']),
            # Double-quoted string, possibly containing escape sequences
            (r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
            # Single-quoted string, possibly containing escape sequences
            (r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string2']),
            # 'def' followed by an identifier
            (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
            # 'class' followed by an identifier
            (r'\bclass\b\s*(\w+)', 1, STYLES['defclass']),
            # From '#' until a newline
            (r'#[^\n]*', 0, STYLES['comment']),
            # Numeric literals
            (r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']),
            (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
            (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0,
             STYLES['numbers']),
        ]
        rules += header_rules1
        rules += header_rules2
        self.rules = [(QRegExp(pat), index, fmt)
                      for (pat, index, fmt) in rules]

    def highlightBlock(self, text):
        for expression, nth, format in self.rules:
            index = expression.indexIn(text, 0)

            while index >= 0:
                # We actually want the index of the nth match
                index = expression.pos(nth) + expression.cap(nth).index(expression.cap(nth))
                length = len(expression.cap(nth))
                self.setFormat(index, length, format)
                index = expression.indexIn(text, index + length)

        self.setCurrentBlockState(0)

if __name__ == "__main__":
    from PyQt5 import QtWidgets

    app = QtWidgets.QApplication([])
    editor = QtWidgets.QPlainTextEdit()
    editor.setStyleSheet("""QPlainTextEdit{
        font-family:'Consolas'; 
        background-color: rgb(204,232,207);}""")
    highlight = CppHighlighter(editor.document())
    editor.show()

    # Load sample.cpp into the editor for demo purposes
    infile = open('your_file_path', 'r', encoding='utf-8')
    editor.setPlainText(infile.read())

    app.exec_()

QsciScintilla

全部重写

你没看错全部重写…

  1. QsciScintilla类重写动作
class MeQsciScintilla(QsciScintilla):
    # 继承编辑器类 重写键盘按键方法
    def __init__(self, parent=None):
        super(MeQsciScintilla, self).__init__(parent)

    def keyPressEvent(self, e):
        ''' 测试按下按键 '''
        if e.key() == Qt.Key_Escape:
            pass
        super().keyPressEvent(e)

    def wheelEvent(self, e):
        ''' Ctrl + 滚轮 控制字体缩放 '''
        if e.modifiers() == Qt.ControlModifier:
            da = e.angleDelta()
            if da.y() > 0:
                self.zoomIn(1)  # QsciScintilla 自带缩放
            elif da.y() < 0:
                self.zoomOut(1)
        else:
            super().wheelEvent(e)
  1. LexerCustom类重写高亮和自动补全
class MeLexer(QsciLexerCustom):
    def __init__(self, parent):
        super(MeLexer, self).__init__(parent)
        # 父类是编辑器
        # 设置默认颜色
        # 设置默认背景
        # 设置默认字号
        self.setDefaultColor(QColor("#ff000000"))
        self.setDefaultPaper(QColor("#CCE8CF"))  # 背景 豆沙绿
        self.setDefaultFont(QFont("Consolas", 13))

        # 样式表 0-1-2-3-4-5
        # 0: 关键字 1: 运算符 2: 格式符 3: 数字 4: 默认 5: 注释
        # 颜色
        self.setColor(QColor("#3CB371"), 0)
        self.setColor(QColor("#6A5ACD"), 1)
        self.setColor(QColor("#20B2AA"), 2)
        self.setColor(QColor("#4169E1"), 3)
        self.setColor(QColor("#2D7C7F"), 4)
        self.setColor(QColor("#C0C0C0"), 5)

        # 字体 consolas DevC++的默认字体 字号自定
        self.setFont(QFont("Consolas", 13, weight=QFont.Bold), 0)
        self.setFont(QFont("Consolas", 13, weight=QFont.Bold), 1)
        self.setFont(QFont("Consolas", 13, weight=QFont.Bold), 2)
        self.setFont(QFont("Consolas", 13, weight=QFont.Bold), 3)
        self.setFont(QFont("Consolas", 13, weight=QFont.Bold), 4)
        self.setFont(QFont("Consolas", 13), 5)
        self.font(5).setItalic(True)

        # 定义关键词列表
        self.keywords_list = [
            'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do',
            'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'int',
            'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static',
            'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile',
            'while', 'bool', 'catch', 'class', 'const_cast', 'delete', 'dynamic_cast',
            'explicit', 'false', 'friend', 'inline', 'mutable', 'namespace', 'new',
            'operator', 'private', 'protected', 'public', 'reinterpret_cast',
            'static_cast', 'template', 'this', 'throw', 'true', 'try', 'typeid',
            'typename', 'using', 'virtual', 'wchar_t', 'include', 'std',
            "byte", "word", "dword",
            "int8_t", "uint8_t", "int16_t", "uint16_t",
            "int32_t", "uint32_t", "int64_t", "uint64_t",
            "int8", "uint8", "int16", "uint16",
            "int32", "uint32", "int64", "uint64"
        ]
        # 定义运算符列表
        self.operator_list = [
            '=',
            # Comparison
            '==', '!=', '<', '<=', '>', '>=',
            # Arithmetic
            '+', '-', '*', '/', '%',
            # In-place
            '+=', '-=', '*=', '/=', '%=',
            # Bitwise
            '^', '|', '&', '~', '>>', '<<', '"', '%s', '%f', '%d', '%ld'
        ]
        # 定义格式符列表
        self.format_list = [
            '{', '}', '(', ')', '[', ']', '#', ';', ','
        ]

    def description(self, style):
        if style == 0:
            return "keyword_style"
        elif style == 1:
            return "operate_style"
        elif style == 2:
            return "format_style"
        elif style == 3:
            return "number_style"
        elif style == 4:
            return "default_style"
        elif style == 5:
            return "tips_style"
        ### 无需返回值 但需要定义内容
        return ""

    def styleText(self, start, end):
        # 1. 初始化风格类
        self.startStyling(start)

        # 2. 切片数据
        text = self.parent().text()[start:end]

        # 3. 词法分析
        p = re.compile(r"\*\/|\/\*|//.*?(?=\r?\n|$)|\s+|\w+|\W")  # // and /**/

        # 关键词列表里是这样的元组  (token_name, token_len) :(关键词内容,关键词长度)
        token_list = [(token, len(bytearray(token, "utf-8"))) for token in p.findall(text)]
        # 4. 风格化
        # 4.1 分支
        multiline_comm_flag = False
        editor = self.parent()
        if start > 0:
            previous_style_nr = editor.SendScintilla(editor.SCI_GETSTYLEAT, start - 1)
            if previous_style_nr == 3:
                multiline_comm_flag = True
        # 4.2 循环风格化
        for i, token in enumerate(token_list):
            if multiline_comm_flag:
                # 处于块注释状态,使用样式5进行风格化
                self.setStyling(token[1], 5)
                if token[0] == "*/":
                    multiline_comm_flag = False
            elif token[0].startswith("//"):
                line_number = self.parent().SendScintilla(self.parent().SCI_LINEFROMPOSITION, start)
                line_start = self.parent().SendScintilla(self.parent().SCI_POSITIONFROMLINE, line_number)
                line_end = self.parent().SendScintilla(self.parent().SCI_GETLINEENDPOSITION, line_number)
                self.startStyling(line_start)
                self.setStyling(line_end - line_start + 1, 5)
                break  # 结束循环,不再继续处理该行后面的内容
            else:
                # 其他情况根据关键词、运算符、格式符、数字进行风格化
                if token[0] in self.keywords_list:
                    self.setStyling(token[1], 0)
                elif token[0] in self.operator_list:
                    self.setStyling(token[1], 1)
                elif token[0] in self.format_list:
                    self.setStyling(token[1], 2)
                elif token[0].isdigit():
                    self.setStyling(token[1], 3)
                elif token[0] == "/*":
                    multiline_comm_flag = True
                    self.setStyling(token[1], 5)
                else:
                    self.setStyling(token[1], 4)
  1. TextEditorWidget类,重写QWidget类,隐藏上面两个重写类,并写交互接口
class TextEditorWidget(QWidget):
    gotoDeclarationSign = QtCore.pyqtSignal()
    gotoDefinitionSign = QtCore.pyqtSignal()
    gotoCallExpressSign = QtCore.pyqtSignal()

    def __init__(self, filename, filepath):
        super(TextEditorWidget, self).__init__(parent=None)
        # 配置
        config_obj = Config()
        self.config_ini = config_obj.read_config()

        # 可访问成员变量
        self.filename = filename
        self.filepath = filepath
        self.status = False

        # 创建布局
        self.__layout = QVBoxLayout(self)
        self.__frame = QFrame(self)
        self.__frameLayout = QVBoxLayout(self.__frame)
        self.__layout.addWidget(self.__frame)

        # 创建编辑器
        self.__editor = MeQsciScintilla(self.__frame)
        # 这里设置自定义词法解析器CPP
        self.__lexer = MeLexer(self.__editor)
        # 配置MeLexer 里面有自定义的高亮风格
        self.__editor.setLexer(self.__lexer)

        # 设置自动补全敏感字数
        self.__editor.setAutoCompletionThreshold(1)
        self.__editor.setAutoCompletionSource(QsciScintilla.AcsAll)
        # 设置自动补全对象
        self.__api = QsciAPIs(self.__lexer)
        # 设置自动补全敏感
        self.__editor.setAutoCompletionCaseSensitivity(True)
        # 设置自动补全替换
        self.__editor.setAutoCompletionReplaceWord(True)
        # 设置自动填充
        self.__editor.setAutoCompletionFillupsEnabled(True)
        # 显示全部调用 不受上下文限制
        self.__editor.setCallTipsStyle(QsciScintilla.CallTipsNoContext)
        # 自动补全选项在下面
        self.__editor.setCallTipsPosition(QsciScintilla.CallTipsBelowText)
        self.__editor.setCallTipsVisible(0)

        # 菜单
        # 设置默认菜单为自定义菜单
        self.__editor.setContextMenuPolicy(Qt.CustomContextMenu)
        # 设置触发
        self.__editor.customContextMenuRequested.connect(self.show_context_menu)

        autocompletions = [
            'include', 'using', 'namespace', 'std',
            'scanf', 'printf', 'return', 'char', '{}',
            '[]', '()', 'int', 'double', 'long', 'float',
            'string', 'endl', 'stdio.h', 'stdlib.h', 'iostream', '<>',
            'free', 'malloc', 'new', 'delete', 'public', 'private', 'protected',
            'cin', 'cout', 'for', 'while', 'do', 'const', 'continue', 'break', 'if', 'else',
            'auto', 'signed', 'short', 'case', 'try', 'catch', 'switch', 'default',
            'true', 'false', 'struct', 'typedef', 'goto', 'sizeof', 'void', 'static', 'union',
            'enum', 'inline', 'extern', 'throw', 'bool', 'class', 'template', 'this', 'vector',
            'math.h', 'abs', 'strcat', 'strcmp', 'strlen', 'strcpy', 'strchr', 'strstr', 'rand',
            'exit', 'time.h', 'string.h', 'ctype.h', 'isdigit', 'isalpha', 'isblank', 'isalnum',
            'getchar', 'fopen', 'fflush', 'fclose', 'remove', 'fprintf', 'puts', 'abort', 'ctime'
        ]
        for ac in autocompletions:
            self.__api.add(ac)
        self.__api.prepare()
        self.__editor.setCallTipsBackgroundColor(QColor('#D8BFD8'))
        # 设置自动补全字体颜色
        self.__editor.setCallTipsForegroundColor(QColor('#F08080'))

        # utf-8
        self.__editor.setUtf8(True)

        # 将编辑器添加到布局中
        self.__frameLayout.addWidget(self.__editor)

        # 细节
        # 设置背景色
        self.__editor.setPaper(QColor("#CCE8CF"))
        # 显示自动换行
        self.__editor.setWrapMode(QsciScintilla.WrapWord)
        self.__editor.setWrapVisualFlags(QsciScintilla.WrapFlagByText)
        self.__editor.setWrapIndentMode(QsciScintilla.WrapIndentIndented)
        # 使用tab
        self.__editor.setIndentationsUseTabs(True)
        # 设置换行符长度4
        self.__editor.setTabWidth(4)
        # 设置Tab自动对齐
        self.__editor.setAutoIndent(True)
        # 设置鼠标光标颜色 前景色...
        self.__editor.setCaretForegroundColor(QColor("#0000CD"))
        # 设置选中行颜色
        self.__editor.setCaretLineVisible(True)
        self.__editor.setCaretLineBackgroundColor(QColor("#AAEDCB"))
        # 行号/页边距颜色
        # 显示行号 行号范围
        self.__editor.setMarginLineNumbers(1, True)
        self.__editor.setMarginWidth(1, '0000')
        self.__editor.setMarginsForegroundColor(QColor("#006400"))
        # 默认未修改
        self.__editor.setModified(False)

    def show_context_menu(self, point):
        self.context_menu = self.__editor.createStandardContextMenu()
        # 添加默认选项
        self.context_menu.insertSeparator(self.context_menu.actions()[0])


        ui_icon = self.config_ini['main_project']['project_name'] + self.config_ini['ui_img']['ui_turn_to']

        action_goto_declaration = QAction("转到声明", self)
        action_goto_declaration.setIcon(QIcon(ui_icon))
        action_goto_declaration.triggered.connect(self.gotoDeclaration)
        action_goto_definition = QAction("转到定义", self)
        action_goto_definition.setIcon(QIcon(ui_icon))
        action_goto_definition.triggered.connect(self.gotoDefinition)
        action_goto_call_express = QAction("转到调用", self)
        action_goto_call_express.setIcon(QIcon(ui_icon))
        action_goto_call_express.triggered.connect(self.gotoCallExpress)
        # 分隔符
        self.context_menu.insertSeparator(self.context_menu.actions()[0])
        self.context_menu.insertAction(self.context_menu.actions()[0], action_goto_declaration)
        self.context_menu.insertAction(self.context_menu.actions()[1], action_goto_definition)
        self.context_menu.insertAction(self.context_menu.actions()[2], action_goto_call_express)
        # 应用
        self.context_menu.exec_(self.__editor.mapToGlobal(point))

    def gotoDeclaration(self):
        self.gotoDeclarationSign.emit()

    def gotoDefinition(self):
        self.gotoDefinitionSign.emit()

    def gotoCallExpress(self):
        self.gotoCallExpressSign.emit()

    def highlight_function_declaration(self, positions):
        # 传入的是整个位置数据....
        indicator_number = 1  # 指示器的编号
        lines = self.__editor.lines() - 1
        indexs = self.__editor.lineLength(lines)
        indicator_color = QColor('#f05b72')  # 蔷薇色
        if positions:
            self.highlight_handle(positions, lines, indexs, indicator_number, indicator_color)

    def highlight_function_definition(self, positions):
        # 传入的是整个位置数据....
        indicator_number = 2  # 指示器的编号
        lines = self.__editor.lines() - 1
        indexs = self.__editor.lineLength(lines)
        indicator_color = QColor('#ed1941')  # 赤色
        if positions:
            self.highlight_handle(positions, lines, indexs, indicator_number, indicator_color)

    def highlight_function_call_express(self, positions):
        # 传入的是整个位置数据....
        indicator_number = 3  # 指示器的编号
        lines = self.__editor.lines() - 1
        indexs = self.__editor.lineLength(lines)
        indicator_color = QColor('#f47920')  # 橙色
        if positions:
            self.highlight_handle(positions, lines, indexs, indicator_number, indicator_color)

    def highlight_handle(self, positions, lines, indexs, indicator_number, indicator_color):
        self.__editor.SendScintilla(QsciScintilla.SCI_SETINDICATORCURRENT, indicator_number)
        # 清除所有指示器的色块填充
        for i in range(1, 4):
            self.__editor.clearIndicatorRange(0, 0, lines, indexs, i)
        self.__editor.SendScintilla(QsciScintilla.SCI_INDICATORCLEARRANGE, 0,
                                    self.__editor.SendScintilla(QsciScintilla.SCI_GETLINECOUNT))

        for start_line, start_index, end_line, end_index in positions:
            self.__editor.SendScintilla(QsciScintilla.SCI_INDICSETSTYLE, indicator_number,
                                        QsciScintilla.INDIC_CONTAINER)
            self.__editor.SendScintilla(QsciScintilla.SCI_INDICSETFORE, indicator_number,
                                        indicator_color)
            self.__editor.fillIndicatorRange(start_line, start_index, end_line, end_index, indicator_number)
            self.__editor.setCursorPosition(end_line, end_index)

    # 获取选中位置文本 返回位置和一模一样文本
    def getSelected_Position_Content(self):
        if self.__editor.getSelection() != (-1, -1, -1, -1):
            selected_text = self.__editor.selectedText()
            start_line = self.__editor.SendScintilla(Qsci.QsciScintilla.SCI_LINEFROMPOSITION,
                                                     self.__editor.SendScintilla(
                                                         Qsci.QsciScintilla.SCI_GETSELECTIONSTART))  # 设置起始行号为当前选中文本所在行
            start_index = self.__editor.SendScintilla(Qsci.QsciScintilla.SCI_GETCOLUMN, self.__editor.SendScintilla(
                Qsci.QsciScintilla.SCI_GETSELECTIONSTART))  # 设置起始索引为当前选中文本的起始位置
            end_line = self.__editor.SendScintilla(Qsci.QsciScintilla.SCI_LINEFROMPOSITION, self.__editor.SendScintilla(
                Qsci.QsciScintilla.SCI_GETSELECTIONEND))  # 设置结束行号为当前选中文本的结束行
            end_index = self.__editor.SendScintilla(Qsci.QsciScintilla.SCI_GETCOLUMN, self.__editor.SendScintilla(
                Qsci.QsciScintilla.SCI_GETSELECTIONEND))  # 设置结束索引为当前选中文本的结束位置
            return [(start_line, start_index, end_line, end_index)], selected_text

    def getSelectdFunctionName(self, input_string):
        import re
        pattern = r'\b(\w+)\s*\('
        match = re.search(pattern, input_string)
        if match:
            return match.group(1)
        words = re.findall(r'\b\w+\b', input_string)  # 提取字符串中的单词列表
        for word in words:
            if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', word):  # 判断单词是否符合函数名的命名规则
                return word  # 返回第一个符合要求的单词作为函数名
        return None

    # 这里是添加内容
    def addText(self, content):
        # 防止有憨憨放列表进来...
        input_content = ''
        if isinstance(content, list):
            if '\r' or '\n' in content:
                input_content = ''.join(content)
            else:
                input_content = '\n'.join(content)
        elif isinstance(content, str):
            input_content = content
        self.__editor.setText(input_content)

    # 得到当前文本
    def getText(self):
        content = self.__editor.text()
        content = content.replace('\r', '')
        return content

    # 得到编辑器当前状态
    def getStatus(self):
        # bool
        status = self.__editor.isModified()
        return status

    # 修改当前编辑器状态
    def changeStatus(self, flag):
        self.__editor.setModified(flag)

    # 得到搜索结果....
    def search_interface(self, keyword, *state):
        """
        obj = QsciScintilla()
        flag = obj.findFirst(self,expr,re,cs,wo,wrap,forward,line,index,show,posix,cxx11)->bool
        :param keyword: 你的关键词
        :param state: 元组(***)
        tips -> [虽然是 Any 但默认写 bool]
        expr: Any, --> 匹配词 keyword
        re: Any, --> 是否使用正则表达式匹配 -> 默认向后搜索 regexp
        cs: Any, --> 是否区分大小写匹配
        wo: Any, --> 是否匹配整个关键词 --> 不需要完整匹配 --> False
        wrap: Any, --> 是否在匹配结束之后回到搜索起点 --> 默认是 True
        forward: bool, --> 向前搜索: False, 向后搜索: True; 一般是True即向后搜索... 这里注意不要搞反
        line: -1, --> 搜索的起始行号 表示从当前行开始... 0~N
        index: -1, --> 搜索的起始索引 表示从当前行的当前光标开始 0~N
        show: True, --> 是否显示搜索结果 默认高亮...显示
        posix: False, --> 是否使用POSIX正则表达式匹配 默认False
        username: False, --> 是否用用户名模式匹配 我的用户名: cxx11
        :return: bool --> 返回一个布尔值 表明是否找到...
        """
        return self.__editor.findFirst(keyword, *state)  # bool

    def search_interface_(self):
        # 直接找下一个....
        return self.__editor.findNext()

    def send_signal(self, parameter1, parameter2=None):
        v1 = getattr(self.__editor, parameter1)
        if parameter2 is None:
            return self.__editor.SendScintilla(v1)
        else:
            return self.__editor.SendScintilla(v1, parameter2)

    def send_signal_(self, item1, item2):
        item3 = self.__editor.SendScintilla(item2)
        return self.__editor.SendScintilla(item1, item3)

    # 必须搭配findFirst()/findNext()食用 在目标被选中的前提下替换
    def replace_interface(self, keywords):
        self.__editor.replace(keywords)

    def moveCursor(self, line, index):
        self.__editor.setCursorPosition(line, index)

    def highlight_text(self, positions):
        start_line, start_index, end_line, end_index = positions
        self.__editor.setSelectionBackgroundColor(QColor('#4169E1'))  # 蓝
        self.__editor.setSelectionForegroundColor(QColor('#FF8C00'))  # 橘
        self.__editor.setSelection(start_line, start_index, end_line, end_index)

    def multi_highlight_text(self, positions):
        indicator_number = 1  # 指示器的编号
        lines = self.__editor.lines() - 1
        indexs = self.__editor.lineLength(lines)
        self.__editor.SendScintilla(QsciScintilla.SCI_SETINDICATORCURRENT, indicator_number)
        self.__editor.clearIndicatorRange(0, 0, lines, indexs, indicator_number)
        for start_line, start_index, end_line, end_index in positions:
            self.__editor.SendScintilla(QsciScintilla.SCI_INDICSETSTYLE, indicator_number,
                                        QsciScintilla.INDIC_CONTAINER)
            self.__editor.SendScintilla(QsciScintilla.SCI_INDICSETFORE, indicator_number,
                                        QColor('#4169E1'))
            self.__editor.fillIndicatorRange(start_line, start_index, end_line, end_index, indicator_number)

    def clear_all_indicator_sign(self):
        indicator_number = 1  # 指示器的编号
        lines = self.__editor.lines() - 1
        indexs = self.__editor.lineLength(lines)
        self.__editor.clearIndicatorRange(0, 0, lines, indexs, indicator_number)

    def getSelectionState(self):
        return self.__editor.getSelection()

    def getCursorLocation(self):
        return self.__editor.getCursorPosition()
  1. QtabWidget里使用TextEditorWidget
def openfile(self):
    test_path = self.config_ini["main_project"]["project_name"] + self.config_ini["test"]["folder_path"]
    fileName, isOk = QFileDialog.getOpenFileName(self, "选取文件", test_path, "C/C++源文件 (*.c *.cpp)")
    path = ''
    name = ''
    if fileName:
        flag = self.main_detector(fileName)
    else:
        message_ = CustomMessageBox(icon=QIcon(self.ui_icon),title='提示',text='您没有选择文件!')
        message_.exec_()
        return
    if flag:
        if isOk:
            path, name = split(fileName)
            if path:
             text_editor_obj = TextEditorWidget(filename=name, filepath=path)
             text_editor_obj.addText(content=content)
             self.ui.text_editor.addTab(text_editor_obj, text_editor_obj.filename)
             self.ui.text_editor.setCurrentWidget(text_editor_obj)

查找替换

因为那个重写的QsciScintilla类里有自带的查找findFirst、替换函数replace,但是它并没有说明书,所以又遇到对手了。接下来就需要我们学会自己克服困难,比如说把参数爬下来,丢给大爹让它分析,然后我再自己写一些特殊逻辑。

提前说明:
  1. findFirst
    这个函数有多个参数,传入的方式除了关键词之外,都是bool值,我分析得到的结果如下:
def search_interface(self, keyword, *state):
    """
    obj = QsciScintilla()
    flag = obj.findFirst(self,expr,re,cs,wo,wrap,forward,line,index,show,posix,cxx11)->bool
    :param keyword: 你的关键词
    :param state: 元组(***)
    tips -> [虽然是 Any 但默认写 bool]
    expr: Any, --> 匹配词 keyword
    re: Any, --> 是否使用正则表达式匹配 -> 默认向后搜索 regexp
    cs: Any, --> 是否区分大小写匹配
    wo: Any, --> 是否匹配整个关键词 --> 不需要完整匹配 --> False
    wrap: Any, --> 是否在匹配结束之后回到搜索起点 --> 默认是 True
    forward: bool, --> 向前搜索: False, 向后搜索: True; 一般是True即向后搜索... 这里注意不要搞反
    line: -1, --> 搜索的起始行号 表示从当前行开始... 0~N
    index: -1, --> 搜索的起始索引 表示从当前行的当前光标开始 0~N
    show: True, --> 是否显示搜索结果 默认高亮...显示
    posix: False, --> 是否使用POSIX正则表达式匹配 默认False
    username: False, --> 是否用用户名模式匹配 我的用户名: cxx11
    :return: bool --> 返回一个布尔值 表明是否找到...
    """
    return self.__editor.findFirst(keyword, *state)  # bool
  1. replace
    这个是必须在选中的情况下使用,首先用findFirst查找选中关键词,让这些关键词处于选中状态,然后直接用self.__editor.replace(replace_word)就自动替换完成,注意是选中状态有几个替换几个。
参数说明

区分大小写:就是根据大小写来匹配,如果输入一个全部大写的是不会匹配到小写内容的。
全部匹配:就是必须严格是那个关键词,res只能匹配res不能匹配res1
正则表达式匹配: 根据你输入的关键词得到那个关键词的正则表达式,根据它再查找匹配其他字符。
是否回到搜索起点:默认搜索起点是光标停留处,若不回到就不会循环查找/替换光标范围之外的。
向前/先后搜索:相对于光标停留位置向前或者向后,True是向后
搜索行号:默认-1 默认从第一行,可以自定义,0~N-1
搜索下标:默认-1 默认第一行第一个开始,可以自定义,0~N-1
默认返回值是bool,代表在特定位置上是否找到

范围书写

这里由于替换很简单,只写搜索
替换就是在搜索基础上替换
全部搜索:

def search_all_string(self):
    self.select_keywords_pos.clear()
    self.keywords_pos.clear()
    # 获取输入
    input_string = self.ui.input_s.currentText()
    # 判断重复和大小写
    current_tab = self.father.currentWidget()
    if input_string and current_tab:
        # 太棒了! 我逐渐理解一切。
        # 清空矛盾
        self.select_keywords_pos.clear()
        self.keywords_pos.clear()
        # 关键词
        positions = set()  # 存储匹配的位置
        line = 0  # 设置起始行号为0
        index = 0  # 设置起始索引为0
        count = 0  # 匹配次数计数器
        forward = self.isfoward()
        while True:
            state = (
                self.ui.re_s.isChecked(),  # regexp
                self.ui.cs_s.isChecked(),  # cs
                self.ui.wo_s.isChecked(),  # wo
                False,  # 回到开始?
                forward,  # 向前向后?
                line,  # 行
                index,  # 下标
                True, False, False)
            flag = current_tab.search_interface(input_string, *state)
            if not flag:
                break
            found_pos = current_tab.send_signal(parameter1='SCI_GETCURRENTPOS', parameter2=None)
            found_line = current_tab.send_signal(parameter1='SCI_LINEFROMPOSITION', parameter2=found_pos)
            found_index = found_pos - current_tab.send_signal(parameter1='SCI_POSITIONFROMLINE',
                                                              parameter2=found_line) - 1
            if len(input_string) > 1:
                positions.add(
                    (
                        found_line, found_index - len(input_string) + 1, found_line,
                        found_index + 1))  # 记录匹配的位置(行号和索引)
            else:
                positions.add(
                    (found_line, found_index, found_line, found_index + len(input_string)))  # 记录匹配的位置(行号和索引)
            count += 1
            line = found_line
            index = found_index + len(input_string)

        self.keywords_pos = list(positions)
        current_tab.multi_highlight_text(self.keywords_pos)
        # self.ui.msg1 && self.ui.msg2
        self.ui.msg1.setText(f"共搜索到关键词: '{input_string}'  {count}次!")

选中搜索

def search_select_string(self):
    # 点击之前先清空一切阻碍
    self.select_keywords_pos.clear()
    self.keywords_pos.clear()
    # 获取输入
    input_string = self.ui.input_s.currentText()
    current_tab = self.father.currentWidget()
    if current_tab.getSelectionState() == (-1, -1, -1, -1):
        message_box = CustomMessageBox(icon=QIcon(self.ui_icon), title='提示', text='请先选中一个区域!')
        message_box.exec_()
    elif input_string and current_tab.getSelectionState() != (-1, -1, -1, -1):
        start_line = current_tab.send_signal_(Qsci.QsciScintilla.SCI_LINEFROMPOSITION,
                                              Qsci.QsciScintilla.SCI_GETSELECTIONSTART)
        start_index = current_tab.send_signal_(Qsci.QsciScintilla.SCI_GETCOLUMN,
                                               Qsci.QsciScintilla.SCI_GETSELECTIONSTART)
        end_line = current_tab.send_signal_(Qsci.QsciScintilla.SCI_LINEFROMPOSITION,
                                            Qsci.QsciScintilla.SCI_GETSELECTIONEND)
        end_index = current_tab.send_signal_(Qsci.QsciScintilla.SCI_GETCOLUMN,
                                             Qsci.QsciScintilla.SCI_GETSELECTIONEND)
        # 存储匹配的位置
        positions = set()
        count = 0
        current_line = start_line
        current_index = start_index
        while True:
            if current_line > end_line:
                break
            state = (
                self.ui.re_s.isChecked(),  # regexp
                self.ui.cs_s.isChecked(),  # cs
                self.ui.wo_s.isChecked(),  # wo
                False,  # 回到开始?
                self.isfoward(),  # 向前向后?
                current_line,  # 行
                current_index,  # 下标
                True, False, False)

            if (current_index >= end_index and current_line == end_line) or (current_line > end_line):
                break
            flag = current_tab.search_interface(input_string, *state)

            if flag:
                found_pos = current_tab.send_signal(parameter1='SCI_GETCURRENTPOS', parameter2=None)
                found_line = current_tab.send_signal(parameter1='SCI_LINEFROMPOSITION', parameter2=found_pos)
                found_index = found_pos - current_tab.send_signal(parameter1='SCI_POSITIONFROMLINE',
                                                                  parameter2=found_line) - 1
                current_line = found_line
                current_index = found_index + len(input_string)
                # 再判断 因为这个先加
                if current_line > end_line:
                    break
                if len(input_string) > 1:
                    positions.add(
                        (found_line, found_index - len(input_string) + 1, found_line,
                         found_index + 1))  # 记录匹配的位置(行号和索引)
                else:
                    positions.add(
                        (found_line, found_index, found_line, found_index + len(input_string)))  # 记录匹配的位置(行号和索引)
                count += 1
            else:
                # 搜不到就需要换行....
                if current_index <= end_index and current_line <= end_line:
                    current_index += len(input_string)
                elif current_line <= end_line and current_index > end_index:
                    current_index = 0
                    current_line += 1
        self.select_keywords_pos = list(positions)
        current_tab.multi_highlight_text(self.select_keywords_pos)
        self.ui.msg1.setText(f"共搜索到关键词: '{input_string}'  {count}次!")

干货干得要缺水了!
语法树到时候更是重量级…
在这里插入图片描述
任重而道远…
放在另一篇里面吧…
这里是链接
不知不觉竟然写了这么多代码(嗐)
如果付出和收获对等,我也能成为最强吗?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值