python文本编辑_支持python语法高亮的文本编辑器

本例中创建的窗口部件支持Rich文本的编辑,并且支持语法高亮(基于QSyntaxHighlighter)。

核心代码如下:import os

import sys

from PyQt5.QtCore import (QEvent, QFile, QFileInfo, QIODevice, QRegExp,

QTextStream,Qt)

from PyQt5.QtWidgets import (QAction, QApplication, QFileDialog,

QMainWindow, QMessageBox, QTextEdit)

from PyQt5.QtGui import QFont, QIcon,QColor,QKeySequence,QSyntaxHighlighter,QTextCharFormat,QTextCursor

import qrc_resources

__version__ = "1.1.0"

class PythonHighlighter(QSyntaxHighlighter):

Rules = []

Formats = {}

def __init__(self, parent=None):

super(PythonHighlighter, self).__init__(parent)

self.initializeFormats()

KEYWORDS = ["and", "as", "assert", "break", "class",

"continue", "def", "del", "elif", "else", "except",

"exec", "finally", "for", "from", "global", "if",

"import", "in", "is", "lambda", "not", "or", "pass",

"print", "raise", "return", "try", "while", "with",

"yield"]

BUILTINS = ["abs", "all", "any", "basestring", "bool",

"callable", "chr", "classmethod", "cmp", "compile",

"complex", "delattr", "dict", "dir", "divmod",

"enumerate", "eval", "execfile", "exit", "file",

"filter", "float", "frozenset", "getattr", "globals",

"hasattr", "hex", "id", "int", "isinstance",

"issubclass", "iter", "len", "list", "locals", "map",

"max", "min", "object", "oct", "open", "ord", "pow",

"property", "range", "reduce", "repr", "reversed",

"round", "set", "setattr", "slice", "sorted",

"staticmethod", "str", "sum", "super", "tuple", "type",

"vars", "zip"]

CONSTANTS = ["False", "True", "None", "NotImplemented",

"Ellipsis"]

PythonHighlighter.Rules.append((QRegExp(

"|".join([r"\b%s\b" % keyword for keyword in KEYWORDS])),

"keyword"))

PythonHighlighter.Rules.append((QRegExp(

"|".join([r"\b%s\b" % builtin for builtin in BUILTINS])),

"builtin"))

PythonHighlighter.Rules.append((QRegExp(

"|".join([r"\b%s\b" % constant

for constant in CONSTANTS])), "constant"))

PythonHighlighter.Rules.append((QRegExp(

r"\b[+-]?[0-9]+[lL]?\b"

r"|\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b"

r"|\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b"),

"number"))

PythonHighlighter.Rules.append((QRegExp(

r"\bPyQt4\b|\bQt?[A-Z][a-z]\w+\b"), "pyqt"))

PythonHighlighter.Rules.append((QRegExp(r"\b@\w+\b"),

"decorator"))

stringRe = QRegExp(r"""(?:'[^']*'|"[^"]*")""")

stringRe.setMinimal(True)

PythonHighlighter.Rules.append((stringRe, "string"))

self.stringRe = QRegExp(r"""(:?"["]".*"["]"|'''.*''')""")

self.stringRe.setMinimal(True)

PythonHighlighter.Rules.append((self.stringRe, "string"))

self.tripleSingleRe = QRegExp(r"""'''(?!")""")

self.tripleDoubleRe = QRegExp(r'''"""(?!')''')

@staticmethod

def initializeFormats():

baseFormat = QTextCharFormat()

baseFormat.setFontFamily("courier")

baseFormat.setFontPointSize(12)

for name, color in (("normal", Qt.black),

("keyword", Qt.darkBlue), ("builtin", Qt.darkRed),

("constant", Qt.darkGreen),

("decorator", Qt.darkBlue), ("comment", Qt.darkGreen),

("string", Qt.darkYellow), ("number", Qt.darkMagenta),

("error", Qt.darkRed), ("pyqt", Qt.darkCyan)):

format = QTextCharFormat(baseFormat)

format.setForeground(QColor(color))

if name in ("keyword", "decorator"):

format.setFontWeight(QFont.Bold)

if name == "comment":

format.setFontItalic(True)

PythonHighlighter.Formats[name] = format

def highlightBlock(self, text):

NORMAL, TRIPLESINGLE, TRIPLEDOUBLE, ERROR = range(4)

textLength = len(text)

prevState = self.previousBlockState()

self.setFormat(0, textLength,

PythonHighlighter.Formats["normal"])

if text.startswith("Traceback") or text.startswith("Error: "):

self.setCurrentBlockState(ERROR)

self.setFormat(0, textLength,

PythonHighlighter.Formats["error"])

return

if (prevState == ERROR and

not (text.startswith(sys.ps1) or text.startswith("#"))):

self.setCurrentBlockState(ERROR)

self.setFormat(0, textLength,

PythonHighlighter.Formats["error"])

return

for regex, format in PythonHighlighter.Rules:

i = regex.indexIn(text)

while i >= 0:

length = regex.matchedLength()

self.setFormat(i, length,

PythonHighlighter.Formats[format])

i = regex.indexIn(text, i + length)

# Slow but good quality highlighting for comments. For more

# speed, comment this out and add the following to __init__:

# PythonHighlighter.Rules.append((QRegExp(r"#.*"), "comment"))

if not text:

pass

elif text[0] == "#":

self.setFormat(0, len(text),

PythonHighlighter.Formats["comment"])

else:

stack = []

for i, c in enumerate(text):

if c in ('"', "'"):

if stack and stack[-1] == c:

stack.pop()

else:

stack.append(c)

elif c == "#" and len(stack) == 0:

self.setFormat(i, len(text),

PythonHighlighter.Formats["comment"])

break

self.setCurrentBlockState(NORMAL)

if self.stringRe.indexIn(text) != -1:

return

# This is fooled by triple quotes inside single quoted strings

for i, state in ((self.tripleSingleRe.indexIn(text),

TRIPLESINGLE),

(self.tripleDoubleRe.indexIn(text),

TRIPLEDOUBLE)):

if self.previousBlockState() == state:

if i == -1:

i = text.length()

self.setCurrentBlockState(state)

self.setFormat(0, i + 3,

PythonHighlighter.Formats["string"])

elif i > -1:

self.setCurrentBlockState(state)

self.setFormat(i, text.length(),

PythonHighlighter.Formats["string"])

def rehighlight(self):

QApplication.setOverrideCursor(QCursor(

Qt.WaitCursor))

QSyntaxHighlighter.rehighlight(self)

QApplication.restoreOverrideCursor()

class TextEdit(QTextEdit):

def __init__(self, parent=None):

super(TextEdit, self).__init__(parent)

def event(self, event):

if (event.type() == QEvent.KeyPress and

event.key() == Qt.Key_Tab):

cursor = self.textCursor()

cursor.insertText(" ")

return True

return QTextEdit.event(self, event)

class MainWindow(QMainWindow):

def __init__(self, filename=None, parent=None):

super(MainWindow, self).__init__(parent)

font = QFont("Courier", 11)

font.setFixedPitch(True)

self.editor = TextEdit()

self.editor.setFont(font)

self.highlighter = PythonHighlighter(self.editor.document())

self.setCentralWidget(self.editor)

status = self.statusBar()

status.setSizeGripEnabled(False)

status.showMessage("Ready", 5000)

fileNewAction = self.createAction("&New...", self.fileNew,

QKeySequence.New, "filenew", "Create a Python file")

fileOpenAction = self.createAction("&Open...", self.fileOpen,

QKeySequence.Open, "fileopen",

"Open an existing Python file")

self.fileSaveAction = self.createAction("&Save", self.fileSave,

QKeySequence.Save, "filesave", "Save the file")

self.fileSaveAsAction = self.createAction("Save &As...",

self.fileSaveAs, icon="filesaveas",

tip="Save the file using a new name")

fileQuitAction = self.createAction("&Quit", self.close,

"Ctrl+Q", "filequit", "Close the application")

self.editCopyAction = self.createAction("&Copy",

self.editor.copy, QKeySequence.Copy, "editcopy",

"Copy text to the clipboard")

self.editCutAction = self.createAction("Cu&t", self.editor.cut,

QKeySequence.Cut, "editcut",

"Cut text to the clipboard")

self.editPasteAction = self.createAction("&Paste",

self.editor.paste, QKeySequence.Paste, "editpaste",

"Paste in the clipboard's text")

self.editIndentAction = self.createAction("&Indent",

self.editIndent, "Ctrl+]", "editindent",

"Indent the current line or selection")

self.editUnindentAction = self.createAction("&Unindent",

self.editUnindent, "Ctrl+[", "editunindent",

"Unindent the current line or selection")

fileMenu = self.menuBar().addMenu("&File")

self.addActions(fileMenu, (fileNewAction, fileOpenAction,

self.fileSaveAction, self.fileSaveAsAction, None,

fileQuitAction))

editMenu = self.menuBar().addMenu("&Edit")

self.addActions(editMenu, (self.editCopyAction,

self.editCutAction, self.editPasteAction, None,

self.editIndentAction, self.editUnindentAction))

fileToolbar = self.addToolBar("File")

fileToolbar.setObjectName("FileToolBar")

self.addActions(fileToolbar, (fileNewAction, fileOpenAction,

self.fileSaveAction))

editToolbar = self.addToolBar("Edit")

editToolbar.setObjectName("EditToolBar")

self.addActions(editToolbar, (self.editCopyAction,

self.editCutAction, self.editPasteAction, None,

self.editIndentAction, self.editUnindentAction))

self.editor.selectionChanged.connect(self.updateUi)

self.editor.document().modificationChanged.connect(self.updateUi)

QApplication.clipboard().dataChanged.connect(self.updateUi)

self.resize(800, 600)

self.setWindowTitle("Python Editor")

self.filename = filename

if self.filename is not None:

self.loadFile()

self.updateUi()

def updateUi(self, arg=None):

self.fileSaveAction.setEnabled(

self.editor.document().isModified())

enable = not self.editor.document().isEmpty()

self.fileSaveAsAction.setEnabled(enable)

self.editIndentAction.setEnabled(enable)

self.editUnindentAction.setEnabled(enable)

enable = self.editor.textCursor().hasSelection()

self.editCopyAction.setEnabled(enable)

self.editCutAction.setEnabled(enable)

self.editPasteAction.setEnabled(self.editor.canPaste())

def createAction(self, text, slot=None, shortcut=None, icon=None,

tip=None, checkable=False, signal="triggered()"):

action = QAction(text, self)

if icon is not None:

action.setIcon(QIcon(":/{0}.png".format(icon)))

if shortcut is not None:

action.setShortcut(shortcut)

if tip is not None:

action.setToolTip(tip)

action.setStatusTip(tip)

if slot is not None:

action.triggered.connect(slot)

if checkable:

action.setCheckable(True)

return action

def addActions(self, target, actions):

for action in actions:

if action is None:

target.addSeparator()

else:

target.addAction(action)

def closeEvent(self, event):

if not self.okToContinue():

event.ignore()

def okToContinue(self):

if self.editor.document().isModified():

reply = QMessageBox.question(self,

"Python Editor - Unsaved Changes",

"Save unsaved changes?",

QMessageBox.Yes|QMessageBox.No|

QMessageBox.Cancel)

if reply == QMessageBox.Cancel:

return False

elif reply == QMessageBox.Yes:

return self.fileSave()

return True

def fileNew(self):

if not self.okToContinue():

return

document = self.editor.document()

document.clear()

document.setModified(False)

self.filename = None

self.setWindowTitle("Python Editor - Unnamed")

self.updateUi()

def fileOpen(self):

if not self.okToContinue():

return

dir = (os.path.dirname(self.filename)

if self.filename is not None else ".")

fname = str(QFileDialog.getOpenFileName(self,

"Python Editor - Choose File", dir,

"Python files (*.py *.pyw)")[0])

if fname:

self.filename = fname

self.loadFile()

def loadFile(self):

fh = None

try:

fh = QFile(self.filename)

if not fh.open(QIODevice.ReadOnly):

raise IOError(str(fh.errorString()))

stream = QTextStream(fh)

stream.setCodec("UTF-8")

self.editor.setPlainText(stream.readAll())

self.editor.document().setModified(False)

except EnvironmentError as e:

QMessageBox.warning(self, "Python Editor -- Load Error",

"Failed to load {0}: {1}".format(self.filename, e))

finally:

if fh is not None:

fh.close()

self.setWindowTitle("Python Editor - {0}".format(

QFileInfo(self.filename).fileName()))

def fileSave(self):

if self.filename is None:

return self.fileSaveAs()

fh = None

try:

fh = QFile(self.filename)

if not fh.open(QIODevice.WriteOnly):

raise IOError(str(fh.errorString()))

stream = QTextStream(fh)

stream.setCodec("UTF-8")

stream << self.editor.toPlainText()

self.editor.document().setModified(False)

except EnvironmentError as e:

QMessageBox.warning(self, "Python Editor -- Save Error",

"Failed to save {0}: {1}".format(self.filename, e))

return False

finally:

if fh is not None:

fh.close()

return True

def fileSaveAs(self):

filename = self.filename if self.filename is not None else "."

filename,filetype = QFileDialog.getSaveFileName(self,

"Python Editor -- Save File As", filename,

"Python files (*.py *.pyw)")

if filename:

self.filename = filename

self.setWindowTitle("Python Editor - {0}".format(

QFileInfo(self.filename).fileName()))

return self.fileSave()

return False

def editIndent(self):

cursor = self.editor.textCursor()

cursor.beginEditBlock()

if cursor.hasSelection():

start = pos = cursor.anchor()

end = cursor.position()

if start > end:

start, end = end, start

pos = start

cursor.clearSelection()

cursor.setPosition(pos)

cursor.movePosition(QTextCursor.StartOfLine)

while pos <= end:

cursor.insertText(" ")

cursor.movePosition(QTextCursor.Down)

cursor.movePosition(QTextCursor.StartOfLine)

pos = cursor.position()

cursor.setPosition(start)

cursor.movePosition(QTextCursor.NextCharacter,

QTextCursor.KeepAnchor, end - start)

else:

pos = cursor.position()

cursor.movePosition(QTextCursor.StartOfBlock)

cursor.insertText(" ")

cursor.setPosition(pos + 4)

cursor.endEditBlock()

def editUnindent(self):

cursor = self.editor.textCursor()

cursor.beginEditBlock()

if cursor.hasSelection():

start = pos = cursor.anchor()

end = cursor.position()

if start > end:

start, end = end, start

pos = start

cursor.setPosition(pos)

cursor.movePosition(QTextCursor.StartOfLine)

while pos <= end:

cursor.clearSelection()

cursor.movePosition(QTextCursor.NextCharacter,

QTextCursor.KeepAnchor, 4)

if cursor.selectedText() == " ":

cursor.removeSelectedText()

cursor.movePosition(QTextCursor.Down)

cursor.movePosition(QTextCursor.StartOfLine)

pos = cursor.position()

cursor.setPosition(start)

cursor.movePosition(QTextCursor.NextCharacter,

QTextCursor.KeepAnchor, end - start)

else:

cursor.clearSelection()

cursor.movePosition(QTextCursor.StartOfBlock)

cursor.movePosition(QTextCursor.NextCharacter,

QTextCursor.KeepAnchor, 4)

if cursor.selectedText() == " ":

cursor.removeSelectedText()

cursor.endEditBlock()

if __name__ == "__main__":

app = QApplication(sys.argv)

app.setWindowIcon(QIcon(":/icon.png"))

fname = None

if len(sys.argv) > 1:

fname = sys.argv[1]

form

= MainWindow(fname)

form.show()

app.exec_()

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值