x01.DiamondIDE: hello ide

x01.DiamondIDE

虽然一直在用 vscode,但自己写一个也不错。通过比较,选择 Spyder 来学习。代码: x01.DiamondIDE

1 Edit

1.1 hello DiamondIDE

使用 pip 安装所需模块 pyqt 等,自不待言。hello.py 代码如下:

from qtpy.QtWidgets import QApplication, QPlainTextEdit

app = QApplication(['x01.DiamondIDE'])
edit = QPlainTextEdit('hello DiamondIDE')
edit.show()
app.exec_()

终端输入: python3 hello.py 运行一下,OK!

1.2 添加测试

删除 hello.py, 添加 widgets/edit.py 如下:

# widgets/edit.py (c) 2021 by x01

from qtpy.QtWidgets import QPlainTextEdit, QApplication

class Edit(QPlainTextEdit):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

def test_edit():
    app = QApplication(['x01.DiamondIDE'])
    edit = Edit()
    edit.setPlainText('Hello IDE!')
    edit.show()
    app.exec()

if __name__ == "__main__":
    test_edit()

添加 tests/test_edit.py 如下:

import os, sys 
RootDir = os.path.dirname(os.path.dirname(__file__))
sys.path.append(RootDir)

import widgets.edit as edit 

def test_edit():
    edit.test_edit()

先安装 pytest: python3 -m pip install -U pytest, 然后在终端运行测试: pytest, OK!
顺便添加 main.py,代码如下:

import os, sys 

CurrDir = os.path.dirname(__file__)
sys.path.append(CurrDir)

from widgets.edit import test_edit

def main():
    test_edit()

if __name__ == "__main__":
    main()

运行一下,OK!
注释 tests/test_edit.py 的 RootDir,添加 test.py 以在测试时统一添加路径,代码如下:

import os, sys 
RootDir = os.path.dirname(__file__)
sys.path.append(RootDir)

import pytest

if __name__ == "__main__":
    pytest.main()

运行一下,OK!

1.3 切入点

在 Python 的 site-packages 目录下新建 mypath.pth 文件,添加 x01.DiamondIDE 所在路径,以便导入。
Spyder 太大,还是以 CodeEditor作为切入点。
widgets/edit.py 更改如下:

# widgets/edit.py (c) 2021 by x01

from PyQt5.QtGui import QColor, QFont, QPaintEvent, QPainter, QSyntaxHighlighter, QTextBlock, QTextCharFormat, QTextDocument, QTextFormat
from PyQt5.QtCore import QRect, Qt, QSize
from PyQt5.QtWidgets import QMainWindow, QTextEdit, QPlainTextEdit, QApplication, QWidget
from functools import namedtuple
import re

def get_span(match, key=None):
    if key is not None:
        start, end = match.span(key)
    else:
        start, end = match.span()
    start = len(match.string[:start])
    end = len(match.string[:end])
    return start, end

class Highlighter(QSyntaxHighlighter):
    HighlightingRule = namedtuple('HighlightingRule', ['pattern', 'format'])

    def __init__(self, parent: QTextDocument=None):
        super().__init__(parent)
        self.keywordFormat = QTextCharFormat()
        self.keywordFormat.setForeground(Qt.red)
        self.keywordFormat.setFontWeight(QFont.Bold)
        self.keywords = r'' + '(?P<keyword>' + '|'.join("class int char".split()) + ')' + r''

    def highlightBlock(self, text:str):
        patterns = re.compile(self.keywords, re.S)
        match = patterns.search(text)
        index = 0
        while match:
            for key, value in list(match.groupdict().items()):
                if value:
                    start, end = get_span(match, key)
                    index += end - start 
                    self.setFormat(start, end-start, self.keywordFormat)
            match = patterns.search(text, match.end())


class LineNumberArea(QWidget):
    def __init__(self, editor=None):
        super().__init__(editor)
        self.editor = editor
        self.left_padding = 3
        self.right_padding = 6

    # override
    def sizeHint(self):
        return QSize(self.editor.LineNumberAreaWidth(), 0)

    def paintEvent(self, event):
        self.editor.LineNumberAreaPaintEvent(event)


class CodeEditor(QPlainTextEdit):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.line_number_area = LineNumberArea(self)
        self.line_number_enabled = True 

        #event
        self.blockCountChanged.connect(self.UpdateLineNumberAreaWidth)
        self.updateRequest.connect(self.UpdateLineNumberArea)
        self.cursorPositionChanged.connect(self.HighlightCurrentLine)

        self.UpdateLineNumberAreaWidth(0)
        self.HighlightCurrentLine()
        self.highlighting = Highlighter(self.document())

    def resizeEvent(self, event):
        super().resizeEvent(event)
        cr:QRect = self.contentsRect()
        self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.LineNumberAreaWidth(), cr.height()))

    def LineNumberAreaWidth(self):
        width = 0
        if self.line_number_enabled:
            digits = 1
            count = max(1, self.blockCount())
            while count >= 10:
                count /= 10
                digits += 1
            fm = self.fontMetrics()
            width = fm.width('9') * digits + self.line_number_area.left_padding  + self.line_number_area.right_padding
        return width 

    def LineNumberAreaPaintEvent(self, event:QPaintEvent):
        if self.line_number_enabled:
            painter = QPainter(self.line_number_area)
            painter.fillRect(event.rect(), Qt.lightGray)

            block:QTextBlock  = self.firstVisibleBlock()
            block_number = block.blockNumber()
            top = round(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
            bottom = top + round(self.blockBoundingRect(block).height())

            while block.isValid() and top <= event.rect().bottom():
                if block.isVisible() and bottom >= event.rect().top():
                    number = block_number + 1
                    painter.setPen(Qt.black)
                    painter.drawText(0, top, self.line_number_area.width() - self.line_number_area.right_padding, 
                            self.fontMetrics().height(), Qt.AlignRight, str(number))
                block = block.next()
                top = bottom 
                bottom = top + round(self.blockBoundingRect(block).height())
                block_number += 1


    def UpdateLineNumberAreaWidth(self, new_block_count=None):
        self.setViewportMargins(self.LineNumberAreaWidth(),0,0,0)

    def UpdateLineNumberArea(self, rect, dy):
        if self.line_number_enabled:
            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 HighlightCurrentLine(self):
        extra = []
        if not self.isReadOnly():
            lineColor = QColor(Qt.yellow).lighter(160)
            selection = QTextEdit.ExtraSelection()
            selection.format.setBackground(lineColor)
            selection.format.setProperty(QTextFormat.FullWidthSelection, True)
            selection.cursor = self.textCursor()
            selection.cursor.clearSelection()
            extra.append(selection)
        self.setExtraSelections(extra)

def test_edit():
    app = QApplication(['x01.DiamondIDE'])
    ed = CodeEditor()
    ed.setPlainText('Hello IDE!')
    ed.show()
    app.exec_()

if __name__ == "__main__":
    test_edit()

运行一下,OK!现在已经可以显示行号和语法高亮了。

原文地址:https://www.cnblogs.com/china_x01/p/14258318.html