SIT/ui/widgets/code_editor_widget.py

159 lines
5.2 KiB
Python
Raw Permalink Normal View History

2026-01-29 09:08:31 +00:00
"""
代码编辑器控件
带语法高亮的代码编辑器
"""
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