""" 代码编辑器控件 带语法高亮的代码编辑器 """ from PyQt5.QtWidgets import QPlainTextEdit, QWidget, QVBoxLayout from PyQt5.QtCore import Qt, QRect from PyQt5.QtGui import ( QColor, QTextFormat, QPainter, QFont, QSyntaxHighlighter, QTextCharFormat ) import re class LineNumberArea(QWidget): """行号区域""" def __init__(self, editor): super().__init__(editor) self.editor = editor def sizeHint(self): return self.editor.lineNumberAreaWidth() def paintEvent(self, event): self.editor.lineNumberAreaPaintEvent(event) class CppHighlighter(QSyntaxHighlighter): """C++语法高亮器""" def __init__(self, document): super().__init__(document) # 定义高亮规则 self.highlighting_rules = [] # 关键字 keyword_format = QTextCharFormat() keyword_format.setForeground(QColor(86, 156, 214)) keyword_format.setFontWeight(QFont.Bold) keywords = [ 'class', 'struct', 'enum', 'namespace', 'public', 'private', 'protected', 'virtual', 'override', 'const', 'static', 'inline', 'explicit', 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default', 'return', 'break', 'continue', 'goto', 'int', 'float', 'double', 'char', 'bool', 'void', 'auto', 'true', 'false', 'nullptr', 'this', 'include', 'define', 'ifdef', 'ifndef', 'endif' ] for keyword in keywords: pattern = f'\\b{keyword}\\b' self.highlighting_rules.append((re.compile(pattern), keyword_format)) # 字符串 string_format = QTextCharFormat() string_format.setForeground(QColor(206, 145, 120)) self.highlighting_rules.append((re.compile(r'"[^"\\]*(\\.[^"\\]*)*"'), string_format)) # 注释 comment_format = QTextCharFormat() comment_format.setForeground(QColor(106, 153, 85)) self.highlighting_rules.append((re.compile(r'//[^\n]*'), comment_format)) # 数字 number_format = QTextCharFormat() number_format.setForeground(QColor(181, 206, 168)) self.highlighting_rules.append((re.compile(r'\b\d+\.?\d*\b'), number_format)) def highlightBlock(self, text): """高亮文本块""" for pattern, format in self.highlighting_rules: for match in pattern.finditer(text): start = match.start() length = match.end() - start self.setFormat(start, length, format) class CodeEditorWidget(QPlainTextEdit): """代码编辑器控件""" def __init__(self, language='cpp'): super().__init__() self.language = language # 设置字体 font = QFont("Consolas", 10) font.setFixedPitch(True) self.setFont(font) # 创建行号区域 self.line_number_area = LineNumberArea(self) # 连接信号 self.blockCountChanged.connect(self.updateLineNumberAreaWidth) self.updateRequest.connect(self.updateLineNumberArea) # 初始化行号宽度 self.updateLineNumberAreaWidth(0) # 设置语法高亮 if language == 'cpp': self.highlighter = CppHighlighter(self.document()) def lineNumberAreaWidth(self): """计算行号区域宽度""" digits = len(str(max(1, self.blockCount()))) space = 3 + self.fontMetrics().horizontalAdvance('9') * digits return space def updateLineNumberAreaWidth(self, _): """更新行号区域宽度""" self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) def updateLineNumberArea(self, rect, dy): """更新行号区域""" if dy: self.line_number_area.scroll(0, dy) else: self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height()) if rect.contains(self.viewport().rect()): self.updateLineNumberAreaWidth(0) def resizeEvent(self, event): """窗口大小改变事件""" super().resizeEvent(event) cr = self.contentsRect() self.line_number_area.setGeometry( QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height()) ) def lineNumberAreaPaintEvent(self, event): """绘制行号""" painter = QPainter(self.line_number_area) painter.fillRect(event.rect(), QColor(240, 240, 240)) block = self.firstVisibleBlock() block_number = block.blockNumber() top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() bottom = top + self.blockBoundingRect(block).height() while block.isValid() and top <= event.rect().bottom(): if block.isVisible() and bottom >= event.rect().top(): number = str(block_number + 1) painter.setPen(QColor(100, 100, 100)) painter.drawText(0, int(top), self.line_number_area.width() - 3, self.fontMetrics().height(), Qt.AlignRight, number) block = block.next() top = bottom bottom = top + self.blockBoundingRect(block).height() block_number += 1