C++编辑器-4
2026-02-15 14:28:46
发布于:浙江
该代码并不完整,点此进入代码框架页面
class LineNumberArea(QWidget):
"""行号区域部件"""
def __init__(self, editor):
super().__init__(editor)
self.editor = editor
self.setAttribute(Qt.WA_StyledBackground, True)
self.setStyleSheet("background-color: #f0f0f0;")
def sizeHint(self):
return QSize(self.editor.line_number_area_width(), 0)
def paintEvent(self, event):
self.editor.line_number_area_paint_event(event)
class CppEditor(QPlainTextEdit):
"""带有行号的C++编辑器"""
def __init__(self, font_family="Courier New", font_size=10, enable_auto_completion=True):
super().__init__()
self.line_number_area = LineNumberArea(self)
self.blockCountChanged.connect(self.update_line_number_area_width)
self.updateRequest.connect(self.update_line_number_area)
self.cursorPositionChanged.connect(self.highlight_current_line)
self.update_line_number_area_width(0)
# 设置字体
self.set_editor_font(font_family, font_size)
# 设置高亮器
self.highlighter = CppHighlighter(self.document())
# 设置行号区域宽度
self.line_number_area_width_cache = self.calculate_line_number_area_width()
# 设置Tab键宽度为4个空格
self.setTabStopDistance(4 * QFontMetrics(self.font()).horizontalAdvance(' '))
# 存储括号配对映射
self.bracket_pairs = {
'(': ')',
'[': ']',
'{': '}',
'"': '"',
"'": "'"
}
# 启用括号匹配高亮
self.matching_brackets_enabled = True
# 启用自动补全
self.enable_auto_completion = enable_auto_completion
# 高亮当前行
self.highlight_current_line()
def set_editor_font(self, family, size):
"""设置编辑器字体"""
font = QFont()
font.setFamily(family)
font.setStyleHint(QFont.TypeWriter)
font.setPointSize(size)
self.setFont(font)
self.line_number_area_width_cache = self.calculate_line_number_area_width()
self.update_line_number_area_width(0)
# 更新Tab宽度
self.setTabStopDistance(4 * QFontMetrics(font).horizontalAdvance(' '))
def calculate_line_number_area_width(self):
"""计算行号区域的宽度(精确计算)"""
# 计算最大行号宽度
font_metrics = self.fontMetrics()
line_count = max(1, self.blockCount())
# 计算行号文本宽度
if line_count < 10:
digits = 1
elif line_count < 100:
digits = 2
elif line_count < 1000:
digits = 3
elif line_count < 10000:
digits = 4
else:
digits = 5
# 计算单个数字宽度(使用最宽的数字)
# 修复:使用horizontalAdvance代替已弃用的width方法
digit_widths = []
for digit in "0123456789":
digit_widths.append(font_metrics.horizontalAdvance(digit))
max_digit_width = max(digit_widths)
# 宽度 = 左边距 + 数字宽度 × 数字位数 + 右边距
width = 4 + max_digit_width * digits + 4
return width
def line_number_area_width(self):
"""获取行号区域宽度"""
return self.line_number_area_width_cache
def update_line_number_area_width(self, _):
"""更新行号区域的宽度"""
# 重新计算宽度
self.line_number_area_width_cache = self.calculate_line_number_area_width()
self.setViewportMargins(self.line_number_area_width_cache, 0, 0, 0)
def update_line_number_area(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.update_line_number_area_width(0)
def resizeEvent(self, event):
"""重写resize事件"""
super().resizeEvent(event)
cr = self.contentsRect()
self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height()))
def line_number_area_paint_event(self, event):
"""绘制行号区域"""
painter = QPainter(self.line_number_area)
painter.fillRect(event.rect(), QColor("#F0F0F0"))
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("#606060"))
# 计算绘制位置
font_metrics = self.fontMetrics()
# 修复:使用horizontalAdvance代替已弃用的width方法
text_width = font_metrics.horizontalAdvance(number)
area_width = self.line_number_area.width()
# 在行号区域右侧对齐绘制
painter.drawText(area_width - text_width - 4, int(top) + font_metrics.ascent(), number)
block = block.next()
top = bottom
bottom = top + self.blockBoundingRect(block).height()
block_number += 1
def highlight_current_line(self):
"""高亮当前行"""
extra_selections = []
if not self.isReadOnly():
# 高亮当前行
selection = QTextEdit.ExtraSelection()
line_color = QColor(Qt.yellow).lighter(160)
selection.format.setBackground(line_color)
selection.format.setProperty(QTextFormat.FullWidthSelection, True)
selection.cursor = self.textCursor()
selection.cursor.clearSelection()
extra_selections.append(selection)
# 高亮匹配的括号
if self.matching_brackets_enabled:
self.highlight_matching_brackets(extra_selections)
self.setExtraSelections(extra_selections)
def highlight_matching_brackets(self, extra_selections):
"""高亮匹配的括号"""
cursor = self.textCursor()
if cursor.hasSelection():
return
# 获取当前光标位置
position = cursor.position()
document = self.document()
# 检查当前光标前的字符
if position > 0:
cursor.setPosition(position - 1)
char_before = cursor.selectedText()
cursor.clearSelection()
# 检查是否为括号字符
if char_before in self.bracket_pairs.keys() or char_before in self.bracket_pairs.values():
match_pos = self.find_matching_bracket(char_before, position - 1)
if match_pos != -1:
# 高亮匹配的括号
format = QTextCharFormat()
format.setBackground(QColor(255, 255, 200))
format.setForeground(Qt.black)
# 高亮当前括号
cursor.setPosition(position - 1)
cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)
selection = QTextEdit.ExtraSelection()
selection.format = format
selection.cursor = cursor
extra_selections.append(selection)
# 高亮匹配的括号
cursor.setPosition(match_pos)
cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)
selection = QTextEdit.ExtraSelection()
selection.format = format
selection.cursor = cursor
extra_selections.append(selection)
def find_matching_bracket(self, bracket, position):
"""查找匹配的括号位置"""
document = self.document()
# 确定搜索方向和括号类型
if bracket in self.bracket_pairs.keys():
# 左括号,向前搜索匹配的右括号
target = self.bracket_pairs[bracket]
step = 1
else:
# 右括号,向后搜索匹配的左括号
target = [k for k, v in self.bracket_pairs.items() if v == bracket][0]
step = -1
# 获取整个文档文本
text = document.toPlainText()
# 实现括号匹配算法
stack = 0
pos = position + step
while 0 <= pos < len(text):
current_char = text[pos]
# 跳过注释和字符串中的括号
if self.is_in_comment_or_string(pos):
pos += step
continue
if current_char == bracket:
stack += 1
elif current_char == target:
if stack == 0:
return pos
else:
stack -= 1
pos += step
return -1
def is_in_comment_or_string(self, position):
"""检查位置是否在注释或字符串中"""
document = self.document()
block = document.findBlock(position)
block_pos = position - block.position()
block_text = block.text()
# 检查是否在单行注释中
comment_start = block_text.find("//")
if comment_start != -1 and block_pos >= comment_start:
return True
# 检查是否在字符串中
# 简化检查:查找最近的引号
text_before = block_text[:block_pos]
# 检查双引号字符串
double_quote_count = text_before.count('"')
if double_quote_count % 2 == 1:
# 检查是否有转义的引号
escaped = False
i = len(text_before) - 1
while i >= 0 and text_before[i] == '\\':
escaped = not escaped
i -= 1
if not escaped:
return True
# 检查单引号字符串
single_quote_count = text_before.count("'")
if single_quote_count % 2 == 1:
# 检查是否有转义的引号
escaped = False
i = len(text_before) - 1
while i >= 0 and text_before[i] == '\\':
escaped = not escaped
i -= 1
if not escaped:
return True
return False
def keyPressEvent(self, event):
"""处理键盘事件,实现自动缩进和括号补全"""
if not self.enable_auto_completion:
# 如果自动补全被禁用,调用父类方法
super().keyPressEvent(event)
return
cursor = self.textCursor()
# Tab键处理:插入4个空格
if event.key() == Qt.Key_Tab:
# 如果有选中文本,则缩进选中的行
if cursor.hasSelection():
self.indent_selected_lines()
return
# 否则插入4个空格
cursor.insertText(' ')
return
# 自动缩进:回车键
elif event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
# 获取当前行的文本
document = self.document()
block = document.findBlock(cursor.position())
line_text = block.text()
cursor_pos_in_block = cursor.position() - block.position()
# 检查是否在空大括号 {} 内部
if cursor_pos_in_block > 0 and cursor_pos_in_block < len(line_text):
# 检查光标前后字符
if line_text[cursor_pos_in_block-1] == '{' and line_text[cursor_pos_in_block] == '}':
# 在空大括号 {} 中按回车
# 获取当前行的缩进
indent = ''
for char in line_text:
if char in (' ', '\t'):
indent += char
else:
break
# 首先删除右大括号
cursor.beginEditBlock()
# 移动到右大括号位置
cursor.setPosition(block.position() + cursor_pos_in_block)
cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)
cursor.removeSelectedText()
# 插入换行、缩进和新的右大括号
cursor.insertText('\n' + indent + ' \n' + indent + '}')
# 将光标移动到中间行的缩进位置
cursor.movePosition(QTextCursor.Up)
cursor.movePosition(QTextCursor.EndOfLine)
cursor.endEditBlock()
self.setTextCursor(cursor)
return
# 普通回车处理
# 获取当前行的缩进
indent = ''
for char in line_text:
if char in (' ', '\t'):
indent += char
else:
break
# 插入新行和缩进
cursor.insertText('\n' + indent)
# 检查是否需要额外缩进(如果上一行以{结尾)
if line_text.rstrip().endswith('{'):
cursor.insertText(' ')
return
# 括号自动补全
elif event.text() in ('(', '[', '{', '"', "'"):
self.insert_bracket_pair(event.text())
return
# 如果输入右括号,检查是否应该跳过已有的右括号
elif event.text() in (')', ']', '}'):
current_pos = cursor.position()
document = self.document()
# 检查下一个字符是否是匹配的右括号
if current_pos < document.characterCount():
cursor.setPosition(current_pos)
cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)
next_char = cursor.selectedText()
cursor.clearSelection()
# 如果下一个字符就是输入的右括号,则跳过它
if next_char == event.text():
cursor.setPosition(current_pos + 1)
self.setTextCursor(cursor)
return
# 否则正常插入
super().keyPressEvent(event)
return
# 退格键:如果删除左括号且后面有匹配的右括号,则同时删除右括号
elif event.key() == Qt.Key_Backspace:
cursor = self.textCursor()
if not cursor.hasSelection():
position = cursor.position()
if position > 0 and position < self.document().characterCount():
# 检查前一个字符是否是左括号
cursor.setPosition(position - 1)
cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)
char = cursor.selectedText()
cursor.clearSelection()
if char in self.bracket_pairs.keys():
# 检查下一个字符是否是匹配的右括号
cursor.setPosition(position)
cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)
next_char = cursor.selectedText()
cursor.clearSelection()
if next_char == self.bracket_pairs[char]:
# 删除两个括号
cursor.setPosition(position - 1)
cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, 2)
cursor.removeSelectedText()
return
# 正常处理退格键
super().keyPressEvent(event)
return
# 其他按键正常处理
super().keyPressEvent(event)
def insert_bracket_pair(self, bracket):
"""插入括号对"""
cursor = self.textCursor()
# 如果有选中文本,将选中文本用括号包围
if cursor.hasSelection():
selected_text = cursor.selectedText()
# 对于引号,根据选中的内容决定是否添加空格
if bracket in ('"', "'"):
cursor.insertText(bracket + selected_text + bracket)
else:
cursor.insertText(bracket + selected_text + self.bracket_pairs[bracket])
# 将光标移动到括号内
if selected_text:
cursor.setPosition(cursor.position() - len(selected_text) - 1)
cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, len(selected_text))
self.setTextCursor(cursor)
else:
# 没有选中文本,直接插入括号对
cursor.insertText(bracket + self.bracket_pairs[bracket])
# 将光标移动到括号中间
cursor.movePosition(QTextCursor.Left)
self.setTextCursor(cursor)
def indent_selected_lines(self):
"""缩进选中的行"""
cursor = self.textCursor()
document = self.document()
# 获取选中的起始和结束位置
start = cursor.selectionStart()
end = cursor.selectionEnd()
# 获取起始和结束块
start_block = document.findBlock(start)
end_block = document.findBlock(end)
# 如果结束位置在块开头,则不包含该块
if end == end_block.position():
end_block = end_block.previous()
# 批量编辑
cursor.beginEditBlock()
# 为每个选中的行添加缩进
block = start_block
while block.isValid() and block.blockNumber() <= end_block.blockNumber():
cursor.setPosition(block.position())
cursor.insertText(' ')
block = block.next()
cursor.endEditBlock()
# 恢复选中状态
cursor.setPosition(start_block.position())
cursor.setPosition(end_block.position() + end_block.length(), QTextCursor.KeepAnchor)
# 如果结束块不是最后一个块,调整结束位置
if end_block.next().isValid():
cursor.setPosition(end_block.position() + end_block.length(), QTextCursor.KeepAnchor)
self.setTextCursor(cursor)
def set_auto_completion(self, enabled):
"""设置是否启用自动补全"""
self.enable_auto_completion = enabled
这里空空如也





















有帮助,赞一个