159 lines
5.2 KiB
Python
159 lines
5.2 KiB
Python
|
|
"""
|
||
|
|
代码编辑器控件
|
||
|
|
带语法高亮的代码编辑器
|
||
|
|
"""
|
||
|
|
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
|