Skip to content

Instantly share code, notes, and snippets.

@Axel-Erfurt
Last active April 9, 2024 02:03
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Axel-Erfurt/8c84b5e70a1faf894879cd2ab99118c2 to your computer and use it in GitHub Desktop.
Save Axel-Erfurt/8c84b5e70a1faf894879cd2ab99118c2 to your computer and use it in GitHub Desktop.
Python Editor (created with PyQt5)
#!/usr/bin/python3
# -- coding: utf-8 --
from __future__ import print_function
from PyQt5.QtWidgets import (QPlainTextEdit, QWidget, QVBoxLayout, QApplication, QFileDialog, QMessageBox, QLabel, QCompleter,
QHBoxLayout, QTextEdit, QToolBar, QComboBox, QAction, QLineEdit, QDialog, QPushButton,
QToolButton, QMenu, QMainWindow, QInputDialog, QColorDialog, QStatusBar, QSystemTrayIcon)
from PyQt5.QtGui import (QIcon, QPainter, QTextFormat, QColor, QTextCursor, QKeySequence, QClipboard, QTextDocument,
QPixmap, QStandardItemModel, QStandardItem, QCursor)
from PyQt5.QtCore import (Qt, QVariant, QRect, QDir, QFile, QFileInfo, QTextStream, QSettings, QTranslator, QLocale,
QProcess, QPoint, QSize, QCoreApplication, QStringListModel, QLibraryInfo)
from PyQt5 import QtPrintSupport
from sys import argv
import inspect
from syntax_py import *
import os
import sys
import re
import customcompleter_rc
lineBarColor = QColor("#d3d7cf")
lineHighlightColor = QColor("#fce94f")
tab = chr(9)
eof = "\n"
iconsize = QSize(16, 16)
#####################################################################
class TextEdit(QPlainTextEdit):
def __init__(self, parent=None):
super(TextEdit, self).__init__(parent)
self.installEventFilter(self)
self._completer = None
def setCompleter(self, c):
if self._completer is not None:
self._completer.activated.disconnect()
self._completer = c
# c.popup().verticalScrollBar().hide()
c.popup().setStyleSheet("background-color: #555753; color: #eeeeec; font-size: 8pt; selection-background-color: #4e9a06;")
c.setWidget(self)
c.setCompletionMode(QCompleter.PopupCompletion)
c.activated.connect(self.insertCompletion)
def completer(self):
return self._completer
def insertCompletion(self, completion):
if self._completer.widget() is not self:
return
tc = self.textCursor()
extra = len(completion) - len(self._completer.completionPrefix())
tc.movePosition(QTextCursor.Left)
tc.movePosition(QTextCursor.EndOfWord)
tc.insertText(completion[-extra:])
self.setTextCursor(tc)
def textUnderCursor(self):
tc = self.textCursor()
tc.select(QTextCursor.WordUnderCursor)
return tc.selectedText()
def focusInEvent(self, e):
if self._completer is not None:
self._completer.setWidget(self)
super(TextEdit, self).focusInEvent(e)
def keyPressEvent(self, e):
if e.key() == Qt.Key_Tab:
self.textCursor().insertText(" ")
return
if self._completer is not None and self._completer.popup().isVisible():
# The following keys are forwarded by the completer to the widget.
if e.key() in (Qt.Key_Enter, Qt.Key_Return):
e.ignore()
# Let the completer do default behavior.
return
isShortcut = ((e.modifiers() & Qt.ControlModifier) != 0 and e.key() == Qt.Key_Escape)
if self._completer is None or not isShortcut:
# Do not process the shortcut when we have a completer.
super(TextEdit, self).keyPressEvent(e)
ctrlOrShift = e.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier)
if self._completer is None or (ctrlOrShift and len(e.text()) == 0):
return
eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift
completionPrefix = self.textUnderCursor()
if not isShortcut and (hasModifier or len(e.text()) == 0 or len(completionPrefix) < 2 or e.text()[-1] in eow):
self._completer.popup().hide()
return
if completionPrefix != self._completer.completionPrefix():
self._completer.setCompletionPrefix(completionPrefix)
self._completer.popup().setCurrentIndex(
self._completer.completionModel().index(0, 0))
cr = self.cursorRect()
cr.setWidth(self._completer.popup().sizeHintForColumn(0) + self._completer.popup().verticalScrollBar().sizeHint().width())
self._completer.complete(cr)
####################################################################
class NumberBar(QWidget):
def __init__(self, parent = None):
super(NumberBar, self).__init__(parent)
self.editor = parent
layout = QVBoxLayout()
self.editor.blockCountChanged.connect(self.update_width)
self.editor.updateRequest.connect(self.update_on_scroll)
self.update_width('1')
def update_on_scroll(self, rect, scroll):
if self.isVisible():
if scroll:
self.scroll(0, scroll)
else:
self.update()
def update_width(self, string):
width = self.fontMetrics().width(str(string)) + 8
if self.width() != width:
self.setFixedWidth(width)
def paintEvent(self, event):
if self.isVisible():
block = self.editor.firstVisibleBlock()
height = self.fontMetrics().height()
number = block.blockNumber()
painter = QPainter(self)
painter.fillRect(event.rect(), lineBarColor)
painter.drawRect(0, 0, event.rect().width() - 1, event.rect().height() - 1)
font = painter.font()
current_block = self.editor.textCursor().block().blockNumber() + 1
condition = True
while block.isValid() and condition:
block_geometry = self.editor.blockBoundingGeometry(block)
offset = self.editor.contentOffset()
block_top = block_geometry.translated(offset).top()
number += 1
rect = QRect(0, block_top + 2, self.width() - 5, height)
if number == current_block:
font.setBold(True)
else:
font.setBold(False)
painter.setFont(font)
painter.drawText(rect, Qt.AlignRight, '%i'%number)
if block_top > event.rect().bottom():
condition = False
block = block.next()
painter.end()
class myEditor(QMainWindow):
def __init__(self, parent = None):
super(myEditor, self).__init__(parent)
self.root = QFileInfo.path(QFileInfo(QCoreApplication.arguments()[0]))
self.wordList = []
print("self.root is: ", self.root)
self.appfolder = self.root
self.statusBar().showMessage(self.appfolder)
self.lineLabel = QLabel("line")
self.statusBar().addPermanentWidget(self.lineLabel)
self.MaxRecentFiles = 15
self.windowList = []
self.recentFileActs = []
self.settings = QSettings("PyEdit", "PyEdit")
self.dirpath = QDir.homePath() + "/Dokumente/python_files/"
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowIcon(QIcon.fromTheme("applications-python"))
# Editor Widget ...
self.editor = TextEdit()
self.completer = QCompleter(self)
self.completer.setModel(self.modelFromFile(self.root + '/resources/wordlist.txt'))
self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel)
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setWrapAround(False)
self.completer.setCompletionRole(Qt.EditRole)
self.editor.setCompleter(self.completer)
self.editor.setStyleSheet(stylesheet2(self))
# self.editor.setTabStopWidth(20)
self.editor.cursorPositionChanged.connect(self.cursorPositionChanged)
self.extra_selections = []
self.mainText = "#!/usr/bin/python3\n# -*- coding: utf-8 -*-\n"
self.fname = ""
self.filename = ""
self.mypython = "2"
self.mylabel = QTextEdit()
self.mylabel.setFixedHeight(90)
self.mylabel.setTextInteractionFlags(Qt.TextSelectableByMouse)
# Line Numbers ...
self.numbers = NumberBar(self.editor)
self.createActions()
# Syntax Highlighter ...
self.highlighter = Highlighter(self.editor.document())
# Laying out...
layoutH = QHBoxLayout()
layoutH.setSpacing(1.5)
layoutH.addWidget(self.numbers)
layoutH.addWidget(self.editor)
### systray
self.createTrayIcon()
self.trayIcon.show()
### statusbar
self.statusBar()
self.statusBar().setStyleSheet(stylesheet2(self))
self.statusBar().showMessage('Welcome')
### begin toolbar
tb = self.addToolBar("File")
tb.setStyleSheet(stylesheet2(self))
tb.setContextMenuPolicy(Qt.PreventContextMenu)
tb.setIconSize(QSize(iconsize))
tb.setMovable(True)
tb.setAllowedAreas(Qt.AllToolBarAreas)
tb.setFloatable(True)
### file buttons
self.newAct = QAction("&New", self, shortcut=QKeySequence.New,
statusTip="new file", triggered=self.newFile)
self.newAct.setIcon(QIcon.fromTheme(self.root + "/icons/new24"))
tb.addAction(self.newAct)
self.openAct = QAction("&Open", self, shortcut=QKeySequence.Open,
statusTip="open file", triggered=self.openFile)
self.openAct.setIcon(QIcon.fromTheme(self.root + "/icons/open24"))
tb.addAction(self.openAct)
self.saveAct = QAction("&Save", self, shortcut=QKeySequence.Save,
statusTip="save file", triggered=self.fileSave)
self.saveAct.setIcon(QIcon.fromTheme(self.root + "/icons/floppy24"))
tb.addAction(self.saveAct)
self.saveAsAct = QAction("&Save as ...", self, shortcut=QKeySequence.SaveAs,
statusTip="save file as ...", triggered=self.fileSaveAs)
self.saveAsAct.setIcon(QIcon.fromTheme(self.root + "/icons/floppy25"))
tb.addAction(self.saveAsAct)
self.jumpToAct = QAction("go to Definition", self, shortcut="F12",
statusTip="go to def", triggered=self.gotoBookmarkFromMenu)
self.jumpToAct.setIcon(QIcon.fromTheme("go-next"))
### comment buttons
tb.addSeparator()
self.commentAct = QAction("#comment Line", self, shortcut="F2",
statusTip="comment Line (F2)", triggered=self.commentLine)
self.commentAct.setIcon(QIcon.fromTheme(self.root + "/icons/comment"))
tb.addAction(self.commentAct)
self.uncommentAct = QAction("uncomment Line", self, shortcut="F3",
statusTip="uncomment Line (F3)", triggered=self.uncommentLine)
self.uncommentAct.setIcon(QIcon.fromTheme(self.root + "/icons/uncomment"))
tb.addAction(self.uncommentAct)
self.commentBlockAct = QAction("comment Block", self, shortcut="F6",
statusTip="comment selected block (F6)", triggered=self.commentBlock)
self.commentBlockAct.setIcon(QIcon.fromTheme(self.root + "/icons/commentBlock"))
tb.addAction(self.commentBlockAct)
self.uncommentBlockAct = QAction("uncomment Block (F7)", self, shortcut="F7",
statusTip="uncomment selected block (F7)", triggered=self.uncommentBlock)
self.uncommentBlockAct.setIcon(QIcon.fromTheme(self.root + "/icons/uncommentBlock"))
tb.addAction(self.uncommentBlockAct)
### color chooser
tb.addSeparator()
tb.addAction(QIcon.fromTheme(self.root + "/icons/color1"),"insert QColor", self.insertColor)
tb.addSeparator()
tb.addAction(QIcon.fromTheme("preferences-color"),"change Color", self.changeColor)
###insert templates
tb.addSeparator()
self.templates = QComboBox()
self.templates.setStyleSheet(stylesheet2(self))
self.templates.setFixedWidth(120)
self.templates.setToolTip("insert template")
self.templates.activated[str].connect(self.insertTemplate)
tb.addWidget(self.templates)
### path python buttons
tb.addSeparator()
self.py2Act = QAction("run in Python 2 (F4)", self, shortcut="F4",
statusTip="run in Python 2 (F4)", triggered=self.runPy2)
self.py2Act.setIcon(QIcon.fromTheme("applications-python"))
tb.addAction(self.py2Act)
self.py3Act = QAction("run in Python 3.6 (F6)", self, shortcut="F6",
statusTip="run in Python 3 (F5)", triggered=self.runPy3)
self.py3Act.setIcon(QIcon.fromTheme(self.root + "/icons/python3"))
tb.addAction(self.py3Act)
tb.addSeparator()
tb.addAction(QIcon.fromTheme("edit-clear"),"clear Output Label", self.clearLabel)
tb.addSeparator()
### print preview
self.printPreviewAct = QAction("Print Preview", self, shortcut="Ctrl+Shift+P",
statusTip="Preview Document", triggered=self.handlePrintPreview)
self.printPreviewAct.setIcon(QIcon.fromTheme("document-print-preview"))
tb.addAction(self.printPreviewAct)
### print
self.printAct = QAction("Print", self, shortcut=QKeySequence.Print,
statusTip="Print Document", triggered=self.handlePrint)
self.printAct.setIcon(QIcon.fromTheme("document-print"))
tb.addAction(self.printAct)
### about buttons
tb.addSeparator()
tb.addAction(QIcon.fromTheme(self.root + "/icons/info2"),"&About PyEdit", self.about)
tb.addSeparator()
### exit button
self.exitAct = QAction("exit", self, shortcut=QKeySequence.Quit,
statusTip="Exit", triggered=self.handleQuit)
self.exitAct.setIcon(QIcon.fromTheme(self.root + "/icons/quit"))
tb.addAction(self.exitAct)
### end toolbar
self.indentAct = QAction(QIcon.fromTheme(self.root + "/icons/format-indent-more"), "indent more", self, triggered = self.indentLine, shortcut = "F8")
self.indentLessAct = QAction(QIcon.fromTheme(self.root + "/icons/format-indent-less"), "indent less", self, triggered = self.indentLessLine, shortcut = "F9")
### find / replace toolbar
self.addToolBarBreak()
tbf = self.addToolBar("Find")
tbf.setStyleSheet(stylesheet2(self))
tbf.setContextMenuPolicy(Qt.PreventContextMenu)
tbf.setIconSize(QSize(iconsize))
self.findfield = QLineEdit()
self.findfield.setStyleSheet(stylesheet2(self))
self.findfield.addAction(QIcon.fromTheme("edit-find"), QLineEdit.LeadingPosition)
self.findfield.setClearButtonEnabled(True)
self.findfield.setFixedWidth(150)
self.findfield.setPlaceholderText("find")
self.findfield.setToolTip("press RETURN to find")
self.findfield.setText("")
ft = self.findfield.text()
self.findfield.returnPressed.connect(self.findText)
tbf.addWidget(self.findfield)
self.replacefield = QLineEdit()
self.replacefield.setStyleSheet(stylesheet2(self))
self.replacefield.addAction(QIcon.fromTheme("edit-find-and-replace"), QLineEdit.LeadingPosition)
self.replacefield.setClearButtonEnabled(True)
self.replacefield.setFixedWidth(150)
self.replacefield.setPlaceholderText("replace with")
self.replacefield.setToolTip("press RETURN to replace the first")
self.replacefield.returnPressed.connect(self.replaceOne)
tbf.addSeparator()
tbf.addWidget(self.replacefield)
tbf.addSeparator()
self.repAllAct = QPushButton("replace all")
self.repAllAct.setFixedWidth(100)
self.repAllAct.setStyleSheet(stylesheet2(self))
self.repAllAct.setIcon(QIcon.fromTheme("gtk-find-and-replace"))
self.repAllAct.setStatusTip("replace all")
self.repAllAct.clicked.connect(self.replaceAll)
tbf.addWidget(self.repAllAct)
tbf.addSeparator()
tbf.addAction(self.indentAct)
tbf.addAction(self.indentLessAct)
tbf.addSeparator()
self.gotofield = QLineEdit()
self.gotofield.setStyleSheet(stylesheet2(self))
self.gotofield.addAction(QIcon.fromTheme("next"), QLineEdit.LeadingPosition)
self.gotofield.setClearButtonEnabled(True)
self.gotofield.setFixedWidth(120)
self.gotofield.setPlaceholderText("go to line")
self.gotofield.setToolTip("press RETURN to go to line")
self.gotofield.returnPressed.connect(self.gotoLine)
tbf.addWidget(self.gotofield)
tbf.addSeparator()
self.bookmarks = QComboBox()
self.bookmarks.setStyleSheet(stylesheet2(self))
self.bookmarks.setFixedWidth(280)
self.bookmarks.setToolTip("go to bookmark")
self.bookmarks.activated[str].connect(self.gotoBookmark)
tbf.addWidget(self.bookmarks)
self.bookAct = QAction("add Bookmark", self,
statusTip="add Bookmark", triggered=self.addBookmark)
self.bookAct.setIcon(QIcon.fromTheme("previous"))
tbf.addAction(self.bookAct)
tbf.addSeparator()
self.bookrefresh = QAction("update Bookmarks", self,
statusTip="update Bookmarks", triggered=self.findBookmarks)
self.bookrefresh.setIcon(QIcon.fromTheme("view-refresh"))
tbf.addAction(self.bookrefresh)
tbf.addAction(QAction(QIcon.fromTheme("document-properties"), "check && reindent Text", self, triggered=self.reindentText))
layoutV = QVBoxLayout()
bar=self.menuBar()
bar.setStyleSheet(stylesheet2(self))
self.filemenu=bar.addMenu("File")
self.filemenu.setStyleSheet(stylesheet2(self))
self.separatorAct = self.filemenu.addSeparator()
self.filemenu.addAction(self.newAct)
self.filemenu.addAction(self.openAct)
self.filemenu.addAction(self.saveAct)
self.filemenu.addAction(self.saveAsAct)
self.filemenu.addSeparator()
for i in range(self.MaxRecentFiles):
self.filemenu.addAction(self.recentFileActs[i])
self.updateRecentFileActions()
self.filemenu.addSeparator()
self.clearRecentAct = QAction("clear Recent Files List", self, triggered=self.clearRecentFiles)
self.clearRecentAct.setIcon(QIcon.fromTheme("edit-clear"))
self.filemenu.addAction(self.clearRecentAct)
self.filemenu.addSeparator()
self.filemenu.addAction(self.exitAct)
editmenu = bar.addMenu("Edit")
editmenu.setStyleSheet(stylesheet2(self))
editmenu.addAction(QAction(QIcon.fromTheme('edit-undo'), "Undo", self, triggered = self.editor.undo, shortcut = "Ctrl+u"))
editmenu.addAction(QAction(QIcon.fromTheme('edit-redo'), "Redo", self, triggered = self.editor.redo, shortcut = "Shift+Ctrl+u"))
editmenu.addSeparator()
editmenu.addAction(QAction(QIcon.fromTheme('edit-copy'), "Copy", self, triggered = self.editor.copy, shortcut = "Ctrl+c"))
editmenu.addAction(QAction(QIcon.fromTheme('edit-cut'), "Cut", self, triggered = self.editor.cut, shortcut = "Ctrl+x"))
editmenu.addAction(QAction(QIcon.fromTheme('edit-paste'), "Paste", self, triggered = self.editor.paste, shortcut = "Ctrl+v"))
editmenu.addAction(QAction(QIcon.fromTheme('edit-delete'), "Delete", self, triggered = self.editor.cut, shortcut = "Del"))
editmenu.addSeparator()
editmenu.addAction(QAction(QIcon.fromTheme('edit-select-all'), "Select All", self, triggered = self.editor.selectAll, shortcut = "Ctrl+a"))
editmenu.addSeparator()
editmenu.addAction(self.commentAct)
editmenu.addAction(self.uncommentAct)
editmenu.addSeparator()
editmenu.addAction(self.commentBlockAct)
editmenu.addAction(self.uncommentBlockAct)
editmenu.addSeparator()
editmenu.addAction(self.py2Act)
editmenu.addAction(self.py3Act)
editmenu.addSeparator()
editmenu.addAction(self.jumpToAct)
editmenu.addSeparator()
editmenu.addAction(self.indentAct)
editmenu.addAction(self.indentLessAct)
layoutV.addLayout(layoutH)
self.mylabel.setMinimumHeight(28)
self.mylabel.setStyleSheet(stylesheet2(self))
layoutV.addWidget(self.mylabel)
### main window
mq = QWidget(self)
mq.setLayout(layoutV)
self.setCentralWidget(mq)
# Event Filter ...
# self.installEventFilter(self)
self.editor.setFocus()
self.cursor = QTextCursor()
self.editor.setTextCursor(self.cursor)
self.editor.setPlainText(self.mainText)
self.editor.moveCursor(self.cursor.End)
self.editor.document().modificationChanged.connect(self.setWindowModified)
# Brackets ExtraSelection ...
self.left_selected_bracket = QTextEdit.ExtraSelection()
self.right_selected_bracket = QTextEdit.ExtraSelection()
### shell settings
self.process = QProcess(self)
self.process.setProcessChannelMode(QProcess.MergedChannels)
self.process.readyRead.connect(self.dataReady)
self.process.started.connect(lambda: self.mylabel.append("starting shell"))
self.process.finished.connect(lambda: self.mylabel.append("shell ended"))
self.editor.setContextMenuPolicy(Qt.CustomContextMenu)
self.editor.customContextMenuRequested.connect(self.contextMenuRequested)
self.loadTemplates()
self.readSettings()
self.statusBar().showMessage("self.root is: " + self.root, 0)
def keyPressEvent(self, event):
if self.editor.hasFocus():
if event.key() == Qt.Key_F10:
self.findNextWord()
def cursorPositionChanged(self):
line = self.editor.textCursor().blockNumber() + 1
pos = self.editor.textCursor().positionInBlock()
self.lineLabel.setText("line " + str(line) + " - position " + str(pos))
def textColor(self):
col = QColorDialog.getColor(QColor("#" + self.editor.textCursor().selectedText()), self)
self.pix.fill(col)
if not col.isValid():
return
else:
colorname = 'QColor("' + col.name() + '")'
self.editor.textCursor().insertText(colorname)
self.pix.fill(col)
def loadTemplates(self):
folder = self.appfolder + "/templates"
if QDir().exists(folder):
self.currentDir = QDir(folder)
count = self.currentDir.count()
fileName = "*"
files = self.currentDir.entryList([fileName],
QDir.Files | QDir.NoSymLinks)
for i in range(count - 2):
file = (files[i])
if file.endswith(".txt"):
self.templates.addItem(file.replace(self.appfolder + "/templates", "").replace(".txt", ""))
def Test(self):
self.editor.selectAll()
def reindentText(self):
if self.editor.toPlainText() == "" or self.editor.toPlainText() == self.mainText:
self.statusBar().showMessage("no code to reindent")
else:
self.editor.selectAll()
tab = "\t"
oldtext = self.editor.textCursor().selectedText()
newtext = oldtext.replace(tab, " ")
self.editor.textCursor().insertText(newtext)
self.statusBar().showMessage("reindented")
def insertColor(self):
col = QColorDialog.getColor(QColor("#000000"), self)
if not col.isValid():
return
else:
colorname = 'QColor("' + col.name() + '")'
self.editor.textCursor().insertText(colorname)
def changeColor(self):
if not self.editor.textCursor().selectedText() == "":
col = QColorDialog.getColor(QColor("#" + self.editor.textCursor().selectedText()), self)
if not col.isValid():
return
else:
colorname = col.name()
self.editor.textCursor().insertText(colorname.replace("#", ""))
else:
col = QColorDialog.getColor(QColor("black"), self)
if not col.isValid():
return
else:
colorname = col.name()
self.editor.textCursor().insertText(colorname)
### QPlainTextEdit contextMenu
def contextMenuRequested(self,point):
cmenu = QMenu()
cmenu = self.editor.createStandardContextMenu()
cmenu.addSeparator()
cmenu.addAction(self.jumpToAct)
cmenu.addSeparator()
if not self.editor.textCursor().selectedText() == "":
cmenu.addAction(QIcon.fromTheme("gtk-find-and-replace"),"replace all occurrences with", self.replaceThis)
cmenu.addSeparator()
cmenu.addAction(QIcon.fromTheme("zeal"),"show help with 'zeal'", self.showZeal)
cmenu.addAction(QIcon.fromTheme("gtk-find-"),"find this (F10)", self.findNextWord)
cmenu.addSeparator()
cmenu.addAction(self.py2Act)
cmenu.addAction(self.py3Act)
cmenu.addSeparator()
cmenu.addAction(self.commentAct)
cmenu.addAction(self.uncommentAct)
cmenu.addSeparator()
if not self.editor.textCursor().selectedText() == "":
cmenu.addAction(self.commentBlockAct)
cmenu.addAction(self.uncommentBlockAct)
cmenu.addSeparator()
cmenu.addAction(self.indentAct)
cmenu.addAction(self.indentLessAct)
cmenu.addSeparator()
cmenu.addAction(QIcon.fromTheme("preferences-color"),"insert QColor", self.insertColor)
cmenu.addSeparator()
cmenu.addAction(QIcon.fromTheme("preferences-color"),"change Color", self.changeColor)
cmenu.exec_(self.editor.mapToGlobal(point))
def replaceThis(self):
rtext = self.editor.textCursor().selectedText()
text = QInputDialog.getText(self, "replace with","replace '" + rtext + "' with:", QLineEdit.Normal, "")
oldtext = self.editor.document().toPlainText()
if not (text[0] == ""):
newtext = oldtext.replace(rtext, text[0])
self.editor.setPlainText(newtext)
self.setModified(True)
def showZeal(self):
if self.editor.textCursor().selectedText() == "":
tc = self.editor.textCursor()
tc.select(QTextCursor.WordUnderCursor)
rtext = tc.selectedText()
print(rtext)
# self.editor.moveCursor(QTextCursor.StartOfWord, QTextCursor.MoveAnchor)
# self.editor.moveCursor(QTextCursor.EndOfWord, QTextCursor.KeepAnchor)
else:
rtext = self.editor.textCursor().selectedText()
cmd = "zeal " + str(rtext)
QProcess().startDetached(cmd)
def findNextWord(self):
if self.editor.textCursor().selectedText() == "":
self.editor.moveCursor(QTextCursor.StartOfWord, QTextCursor.MoveAnchor)
self.editor.moveCursor(QTextCursor.EndOfWord, QTextCursor.KeepAnchor)
rtext = self.editor.textCursor().selectedText()
self.findfield.setText(rtext)
self.findText()
def indentLine(self):
if not self.editor.textCursor().selectedText() == "":
newline = u"\u2029"
list = []
ot = self.editor.textCursor().selectedText()
theList = ot.splitlines()
linecount = ot.count(newline)
for i in range(linecount + 1):
list.insert(i, " " + theList[i])
self.editor.textCursor().insertText(newline.join(list))
self.setModified(True)
# self.editor.find(ot)
self.statusBar().showMessage("tabs indented")
def indentLessLine(self):
if not self.editor.textCursor().selectedText() == "":
newline = u"\u2029"
list = []
ot = self.editor.textCursor().selectedText()
theList = ot.splitlines()
linecount = ot.count(newline)
for i in range(linecount + 1):
list.insert(i, (theList[i]).replace(" ", "", 1))
self.editor.textCursor().insertText(newline.join(list))
self.setModified(True)
# self.editor.find(ot)
self.statusBar().showMessage("tabs deleted")
def dataReady(self):
out = ""
try:
out = str(self.process.readAll(), encoding = 'utf8').rstrip()
except TypeError:
self.msgbox("Error", str(self.process.readAll(), encoding = 'utf8'))
out = str(self.process.readAll()).rstrip()
self.mylabel.moveCursor(self.cursor.Start)
self.mylabel.append(out)
if self.mylabel.find("line", QTextDocument.FindWholeWords):
t = self.mylabel.toPlainText().partition("line")[2].partition("\n")[0].lstrip()
if t.find(",", 0):
tr = t.partition(",")[0]
else:
tr = t.lstrip()
self.gotoErrorLine(tr)
else:
return
self.mylabel.moveCursor(self.cursor.End)
self.mylabel.ensureCursorVisible()
def createActions(self):
for i in range(self.MaxRecentFiles):
self.recentFileActs.append(
QAction(self, visible=False,
triggered=self.openRecentFile))
def addBookmark(self):
linenumber = self.getLineNumber()
linetext = self.editor.textCursor().block().text().strip()
self.bookmarks.addItem(linetext, linenumber)
def getLineNumber(self):
self.editor.moveCursor(self.cursor.StartOfLine)
linenumber = self.editor.textCursor().blockNumber() + 1
return linenumber
def gotoLine(self):
ln = int(self.gotofield.text())
linecursor = QTextCursor(self.editor.document().findBlockByLineNumber(ln-1))
self.editor.moveCursor(QTextCursor.End)
self.editor.setTextCursor(linecursor)
def gotoErrorLine(self, ln):
if ln.isalnum:
t = int(ln)
if t != 0:
linecursor = QTextCursor(self.editor.document().findBlockByLineNumber(t-1))
self.editor.moveCursor(QTextCursor.End)
self.editor.setTextCursor(linecursor)
self.editor.moveCursor(QTextCursor.EndOfLine, QTextCursor.KeepAnchor)
else:
return
def gotoBookmark(self):
if self.editor.find(self.bookmarks.itemText(self.bookmarks.currentIndex())):
pass
else:
self.editor.moveCursor(QTextCursor.Start)
self.editor.find(self.bookmarks.itemText(self.bookmarks.currentIndex()))
self.editor.centerCursor()
self.editor.moveCursor(self.cursor.StartOfLine, self.cursor.MoveAnchor)
def gotoBookmarkFromMenu(self):
if self.editor.textCursor().selectedText() == "":
tc = self.editor.textCursor()
tc.select(QTextCursor.WordUnderCursor)
rtext = tc.selectedText()
else:
rtext = self.editor.textCursor().selectedText()
toFind = rtext
self.bookmarks.setCurrentIndex(0)
if self.bookmarks.findText(toFind, Qt.MatchContains):
row = self.bookmarks.findText(toFind, Qt.MatchContains)
self.statusBar().showMessage("found '" + toFind + "' at bookmark " + str(row))
self.bookmarks.setCurrentIndex(row)
self.gotoBookmark()
else:
self.statusBar().showMessage("def not found")
def clearBookmarks(self):
self.bookmarks.clear()
#### find lines with def or class
def findBookmarks(self):
self.editor.setFocus()
self.editor.moveCursor(QTextCursor.Start)
if not self.editor.toPlainText() == "":
self.clearBookmarks()
newline = "\n" #u"\2029"
fr = "from"
im = "import"
d = "def"
d2 = " def"
c = "class"
sn = str("if __name__ ==")
line = ""
list = []
ot = self.editor.toPlainText()
theList = ot.split(newline)
linecount = ot.count(newline)
for i in range(linecount + 1):
if theList[i].startswith(im):
line = str(theList[i]).replace("'\t','[", "").replace("]", "")
self.bookmarks.addItem(str(line), i)
elif theList[i].startswith(fr):
line = str(theList[i]).replace("'\t','[", "").replace("]", "")
self.bookmarks.addItem(str(line), i)
elif theList[i].startswith(c):
line = str(theList[i]).replace("'\t','[", "").replace("]", "")
self.bookmarks.addItem(str(line), i)
elif theList[i].startswith(tab + d):
line = str(theList[i]).replace(tab, "").replace("'\t','[", "").replace("]", "")
self.bookmarks.addItem(str(line), i)
elif theList[i].startswith(d):
line = str(theList[i]).replace(tab, "").replace("'\t','[", "").replace("]", "")
self.bookmarks.addItem(str(line), i)
elif theList[i].startswith(d2):
line = str(theList[i]).replace(tab, "").replace("'\t','[", "").replace("]", "")
self.bookmarks.addItem(str(line), i)
elif theList[i].startswith(sn):
line = str(theList[i]).replace("'\t','[", "").replace("]", "")
self.bookmarks.addItem(str(line), i)
self.statusBar().showMessage("bookmarks changed")
def clearLabel(self):
self.mylabel.setText("")
def openRecentFile(self):
action = self.sender()
if action:
myfile = action.data()
print(myfile)
if (self.maybeSave()):
if QFile.exists(myfile):
self.openFileOnStart(myfile)
else:
self.msgbox("Info", "File does not exist!")
### New File
def newFile(self):
if self.maybeSave():
self.editor.clear()
self.editor.setPlainText(self.mainText)
self.filename = ""
self.setModified(False)
self.editor.moveCursor(self.cursor.End)
self.statusBar().showMessage("new File created.")
self.editor.setFocus()
self.bookmarks.clear()
self.setWindowTitle("new File[*]")
### open File
def openFileOnStart(self, path=None):
if path:
inFile = QFile(path)
if inFile.open(QFile.ReadWrite | QFile.Text):
text = inFile.readAll()
try:
# Python v3.
text = str(text, encoding = 'utf8')
except TypeError:
# Python v2.
text = str(text)
self.editor.setPlainText(text.replace(tab, " "))
self.setModified(False)
self.setCurrentFile(path)
self.editor.setFocus()
self.findBookmarks()
### save backup
file = QFile(self.filename + "_backup")
if not file.open( QFile.WriteOnly | QFile.Text):
QMessageBox.warning(self, "Error",
"Cannot write file %s:\n%s." % (self.filename, file.errorString()))
return
outstr = QTextStream(file)
QApplication.setOverrideCursor(Qt.WaitCursor)
outstr << self.editor.toPlainText()
QApplication.restoreOverrideCursor()
self.statusBar().showMessage("File '" + path + "' loaded succesfully & bookmarks added & backup created ('" + self.filename + "_backup" + "')")
### add all words to completer ###
# mystr = self.editor.toPlainText()
# self.wordList =mystr.split()
# print(mystr)
# self.completer.setModel(self.modelFromFile(self.root + '/resources/wordlist.txt'))
### open File
def openFile(self, path=None):
if self.maybeSave():
if not path:
path, _ = QFileDialog.getOpenFileName(self, "Open File", self.dirpath,
"Python Files (*.py);; all Files (*)")
if path:
self.openFileOnStart(path)
def fileSave(self):
if (self.filename != ""):
file = QFile(self.filename)
if not file.open( QFile.WriteOnly | QFile.Text):
QMessageBox.warning(self, "Error",
"Cannot write file %s:\n%s." % (self.filename, file.errorString()))
return
outstr = QTextStream(file)
QApplication.setOverrideCursor(Qt.WaitCursor)
outstr << self.editor.toPlainText()
QApplication.restoreOverrideCursor()
self.setModified(False)
self.fname = QFileInfo(self.filename).fileName()
self.setWindowTitle(self.fname + "[*]")
self.statusBar().showMessage("File saved.")
self.setCurrentFile(self.filename)
self.editor.setFocus()
else:
self.fileSaveAs()
### save File
def fileSaveAs(self):
fn, _ = QFileDialog.getSaveFileName(self, "Save as...", self.filename,
"Python files (*.py)")
if not fn:
print("Error saving")
return False
lfn = fn.lower()
if not lfn.endswith('.py'):
fn += '.py'
self.filename = fn
self.fname = QFileInfo(QFile(fn).fileName())
return self.fileSave()
def closeEvent(self, e):
self.writeSettings()
if self.maybeSave():
e.accept()
else:
e.ignore()
### ask to save
def maybeSave(self):
if not self.isModified():
return True
if self.filename.startswith(':/'):
return True
ret = QMessageBox.question(self, "Message",
"<h4><p>The document was modified.</p>\n" \
"<p>Do you want to save changes?</p></h4>",
QMessageBox.Yes | QMessageBox.Discard | QMessageBox.Cancel)
if ret == QMessageBox.Yes:
if self.filename == "":
self.fileSaveAs()
return False
else:
self.fileSave()
return True
if ret == QMessageBox.Cancel:
return False
return True
def about(self):
title = "about PyEdit"
message = """
<span style='color: #3465a4; font-size: 20pt;font-weight: bold;'
>PyEdit 2.1</strong></span></p><h3>Python Editor</h3>created by
<a title='Axel Schneider' href='http://goodoldsongs.jimdo.com' target='_blank'>Axel Schneider</a> with PyQt5<br><br>
<span style='color: #555753; font-size: 9pt;'>©2017 Axel Schneider</strong></span></p>
"""
self.infobox(title, message)
def runPy3(self):
if self.editor.toPlainText() == "":
self.statusBar().showMessage("no Code!")
return
if not self.editor.toPlainText() == self.mainText:
if self.filename:
self.mypython = "3"
self.statusBar().showMessage("running " + self.filename + " in Python 3")
self.fileSave()
cmd = "python3"
self.readData(cmd)
else:
self.filename = "/tmp/tmp.py"
self.fileSave()
self.runPy3()
else:
self.statusBar().showMessage("no code to run")
def runPy2(self):
if self.editor.toPlainText() == "":
self.statusBar().showMessage("no Code!")
return
if not self.editor.toPlainText() == self.mainText:
if self.filename:
self.mypython = "2"
self.statusBar().showMessage("running " + self.filename + " in Python 2")
self.fileSave()
cmd = "python"
self.readData(cmd)
else:
self.filename = "/tmp/tmp.py"
self.fileSave()
self.runPy2()
else:
self.statusBar().showMessage("no code to run")
def readData(self, cmd):
self.mylabel.clear()
dname = QFileInfo(self.filename).filePath().replace(QFileInfo(self.filename).fileName(), "")
self.statusBar().showMessage(str(dname))
QProcess().execute("cd '" + dname + "'")
self.process.start(cmd,['-u', dname + self.strippedName(self.filename)])
def killPython(self):
if (self.mypython == "3"):
cmd = "killall python3"
else:
cmd = "killall python"
self.readData(cmd)
def commentBlock(self):
self.editor.copy()
clipboard = QApplication.clipboard();
originalText = clipboard.text()
mt1 = tab + tab + "'''" + "\n"
mt2 = "\n" + tab + tab + "'''"
mt = mt1 + originalText + mt2
clipboard.setText(mt)
self.editor.paste()
def uncommentBlock(self):
self.editor.copy()
clipboard = QApplication.clipboard();
originalText = clipboard.text()
mt1 = tab + tab + "'''" + "\n"
mt2 = "\n" + tab + tab + "'''"
clipboard.setText(originalText.replace(mt1, "").replace(mt2, ""))
self.editor.paste()
self.statusBar().showMessage("added block comment")
def commentLine(self):
newline = u"\u2029"
comment = "#"
list = []
ot = self.editor.textCursor().selectedText()
if not self.editor.textCursor().selectedText() == "":
### multiple lines selected
theList = ot.splitlines()
linecount = ot.count(newline)
for i in range(linecount + 1):
list.insert(i, comment + theList[i])
self.editor.textCursor().insertText(newline.join(list))
self.setModified(True)
self.statusBar().showMessage("added comment")
else:
### one line selected
self.editor.moveCursor(QTextCursor.StartOfLine)
self.editor.textCursor().insertText("#")
def uncommentLine(self):
comment = "#"
newline = u"\u2029"
list = []
ot = self.editor.textCursor().selectedText()
if not self.editor.textCursor().selectedText() == "":
### multiple lines selected
theList = ot.splitlines()
linecount = ot.count(newline)
for i in range(linecount + 1):
list.insert(i, (theList[i]).replace(comment, "", 1))
self.editor.textCursor().insertText(newline.join(list))
self.setModified(True)
self.statusBar().showMessage("comment removed")
else:
### one line selected
self.editor.moveCursor(QTextCursor.StartOfLine)
self.editor.moveCursor(QTextCursor.Right, QTextCursor.KeepAnchor)
if self.editor.textCursor().selectedText() == comment:
self.editor.textCursor().deleteChar()
self.editor.moveCursor(QTextCursor.StartOfLine)
else:
self.editor.moveCursor(QTextCursor.StartOfLine)
def goToLine(self, ft):
self.editor.moveCursor(int(self.gofield.currentText()),
QTextCursor.MoveAnchor) ### not working
def findText(self):
word = self.findfield.text()
if self.editor.find(word):
linenumber = self.editor.textCursor().blockNumber() + 1
self.statusBar().showMessage("found <b>'" + self.findfield.text() + "'</b> at Line: " + str(linenumber))
self.editor.centerCursor()
else:
self.statusBar().showMessage("<b>'" + self.findfield.text() + "'</b> not found")
self.editor.moveCursor(QTextCursor.Start)
if self.editor.find(word):
linenumber = self.editor.textCursor().blockNumber() + 1
self.statusBar().showMessage("found <b>'" + self.findfield.text() + "'</b> at Line: " + str(linenumber))
self.editor.centerCursor()
def findBookmark(self, word):
if self.editor.find(word):
linenumber = self.getLineNumber() #self.editor.textCursor().blockNumber() + 1
self.statusBar().showMessage("found <b>'" + self.findfield.text() + "'</b> at Line: " + str(linenumber))
def handleQuit(self):
if self.maybeSave():
print("Goodbye ...")
app.quit()
def set_numbers_visible(self, value = True):
self.numbers.setVisible(False)
def match_left(self, block, character, start, found):
map = {'{': '}', '(': ')', '[': ']'}
while block.isValid():
data = block.userData()
if data is not None:
braces = data.braces
N = len(braces)
for k in range(start, N):
if braces[k].character == character:
found += 1
if braces[k].character == map[character]:
if not found:
return braces[k].position + block.position()
else:
found -= 1
block = block.next()
start = 0
def match_right(self, block, character, start, found):
map = {'}': '{', ')': '(', ']': '['}
while block.isValid():
data = block.userData()
if data is not None:
braces = data.braces
if start is None:
start = len(braces)
for k in range(start - 1, -1, -1):
if braces[k].character == character:
found += 1
if braces[k].character == map[character]:
if found == 0:
return braces[k].position + block.position()
else:
found -= 1
block = block.previous()
start = None
cursor = self.editor.textCursor()
block = cursor.block()
data = block.userData()
previous, next = None, None
if data is not None:
position = cursor.position()
block_position = cursor.block().position()
braces = data.braces
N = len(braces)
for k in range(0, N):
if braces[k].position == position - block_position or braces[k].position == position - block_position - 1:
previous = braces[k].position + block_position
if braces[k].character in ['{', '(', '[']:
next = self.match_left(block,
braces[k].character,
k + 1, 0)
elif braces[k].character in ['}', ')', ']']:
next = self.match_right(block,
braces[k].character,
k, 0)
if next is None:
next = -1
if next is not None and next > 0:
if next == 0 and next >= 0:
format = QTextCharFormat()
cursor.setPosition(previous)
cursor.movePosition(QTextCursor.NextCharacter,
QTextCursor.KeepAnchor)
format.setBackground(QColor('white'))
self.left_selected_bracket.format = format
self.left_selected_bracket.cursor = cursor
cursor.setPosition(next)
cursor.movePosition(QTextCursor.NextCharacter,
QTextCursor.KeepAnchor)
format.setBackground(QColor('white'))
self.right_selected_bracket.format = format
self.right_selected_bracket.cursor = cursor
def paintEvent(self, event):
highlighted_line = QTextEdit.ExtraSelection()
highlighted_line.format.setBackground(lineHighlightColor)
highlighted_line.format.setProperty(QTextFormat
.FullWidthSelection,
QVariant(True))
highlighted_line.cursor = self.editor.textCursor()
highlighted_line.cursor.clearSelection()
self.editor.setExtraSelections([highlighted_line,
self.left_selected_bracket,
self.right_selected_bracket])
def document(self):
return self.editor.document
def isModified(self):
return self.editor.document().isModified()
def setModified(self, modified):
self.editor.document().setModified(modified)
def setLineWrapMode(self, mode):
self.editor.setLineWrapMode(mode)
def clear(self):
self.editor.clear()
def setPlainText(self, *args, **kwargs):
self.editor.setPlainText(*args, **kwargs)
def setDocumentTitle(self, *args, **kwargs):
self.editor.setDocumentTitle(*args, **kwargs)
def set_number_bar_visible(self, value):
self.numbers.setVisible(value)
def replaceAll(self):
if not self.editor.document().toPlainText() == "":
if not self.findfield.text() == "":
self.statusBar().showMessage("replacing all")
oldtext = self.editor.document().toPlainText()
newtext = oldtext.replace(self.findfield.text(), self.replacefield.text())
self.editor.setPlainText(newtext)
self.setModified(True)
else:
self.statusBar().showMessage("nothing to replace")
else:
self.statusBar().showMessage("no text")
def replaceOne(self):
if not self.editor.document().toPlainText() == "":
if not self.findfield.text() == "":
self.statusBar().showMessage("replacing all")
oldtext = self.editor.document().toPlainText()
newtext = oldtext.replace(self.findfield.text(), self.replacefield.text(), 1)
self.editor.setPlainText(newtext)
self.setModified(True)
else:
self.statusBar().showMessage("nothing to replace")
else:
self.statusBar().showMessage("no text")
def setCurrentFile(self, fileName):
self.filename = fileName
if self.filename:
self.setWindowTitle(self.strippedName(self.filename) + "[*]")
else:
self.setWindowTitle("no File")
files = self.settings.value('recentFileList', [])
try:
files.remove(fileName)
except ValueError:
pass
if not fileName == "/tmp/tmp.py":
files.insert(0, fileName)
del files[self.MaxRecentFiles:]
self.settings.setValue('recentFileList', files)
for widget in QApplication.topLevelWidgets():
if isinstance(widget, myEditor):
widget.updateRecentFileActions()
def updateRecentFileActions(self):
mytext = ""
files = self.settings.value('recentFileList', [])
numRecentFiles = min(len(files), self.MaxRecentFiles)
for i in range(numRecentFiles):
text = "&%d %s" % (i + 1, self.strippedName(files[i]))
self.recentFileActs[i].setText(text)
self.recentFileActs[i].setData(files[i])
self.recentFileActs[i].setVisible(True)
self.recentFileActs[i].setIcon(QIcon.fromTheme("gnome-mime-text-x-python"))
for j in range(numRecentFiles, self.MaxRecentFiles):
self.recentFileActs[j].setVisible(False)
self.separatorAct.setVisible((numRecentFiles > 0))
def strippedName(self, fullFileName):
return QFileInfo(fullFileName).fileName()
def clearRecentFiles(self):
self.settings.setValue('recentFileList', [])
self.updateRecentFileActions()
def readSettings(self):
if self.settings.value("pos") != "":
pos = self.settings.value("pos", QPoint(200, 200))
self.move(pos)
if self.settings.value("size") != "":
size = self.settings.value("size", QSize(400, 400))
self.resize(size)
def writeSettings(self):
self.settings.setValue("pos", self.pos())
self.settings.setValue("size", self.size())
def msgbox(self,title, message):
QMessageBox.warning(self, title, message)
def infobox(self,title, message):
QMessageBox(QMessageBox.Information, title, message, QMessageBox.NoButton, self, Qt.Dialog|Qt.NoDropShadowWindowHint).show()
def insertTemplate(self):
line = int(self.getLineNumber())
path = self.appfolder + "/templates/" + self.templates.itemText(self.templates.currentIndex()) + ".txt"
if path:
inFile = QFile(path)
if inFile.open(QFile.ReadOnly | QFile.Text):
text = inFile.readAll()
self.editor.setFocus()
try: ### python 3
self.editor.textCursor().insertText(str(text, encoding = 'utf8'))
except TypeError: ### python 2
self.editor.textCursor().insertText(str(text))
self.setModified(True)
self.findBookmarks()
self.statusBar().showMessage("'" + self.templates.itemText(self.templates.currentIndex()) + "' inserted")
inFile.close()
text = ""
self.selectLine(line)
else:
self.statusBar().showMessage("error loadind Template")
def selectLine(self, line):
linecursor = QTextCursor(self.editor.document().findBlockByLineNumber(line-1))
self.editor.moveCursor(QTextCursor.End)
self.editor.setTextCursor(linecursor)
def createTrayIcon(self):
if not QSystemTrayIcon.isSystemTrayAvailable():
QMessageBox.critical(None, "Systray",
"I couldn't detect any system tray on this system.")
else:
self.trayIcon = QSystemTrayIcon(self)
self.trayIcon.setIcon(QIcon.fromTheme("applications-python"))
self.trayIconMenu = QMenu(self)
self.trayIconMenu.addAction(QAction(QIcon.fromTheme("applications-python"), "about PyEdit", self, triggered=self.about))
self.trayIconMenu.addSeparator()
self.trayIconMenu.addAction(QAction(QIcon.fromTheme("application-exit"),"Exit", self, triggered=self.handleQuit))
self.trayIcon.setContextMenu(self.trayIconMenu)
def handlePrint(self):
if self.editor.toPlainText() == "":
self.statusBar().showMessage("no text")
else:
dialog = QtPrintSupport.QPrintDialog()
if dialog.exec_() == QDialog.Accepted:
self.handlePaintRequest(dialog.printer())
self.statusBar().showMessage("Document printed")
def handlePrintPreview(self):
if self.editor.toPlainText() == "":
self.statusBar().showMessage("no text")
else:
dialog = QtPrintSupport.QPrintPreviewDialog()
dialog.setFixedSize(900,650)
dialog.paintRequested.connect(self.handlePaintRequest)
dialog.exec_()
self.statusBar().showMessage("Print Preview closed")
def handlePaintRequest(self, printer):
printer.setDocName(self.filename)
document = self.editor.document()
document.print_(printer)
def modelFromFile(self, fileName):
f = QFile(fileName)
if not f.open(QFile.ReadOnly):
return QStringListModel(self.completer)
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
self.words = []
while not f.atEnd():
line = f.readLine().trimmed()
if line.length() != 0:
try:
line = str(line, encoding='ascii')
except TypeError:
line = str(line)
self.words.append(line)
# print("\n".join(self.wordList))
# self.words.append("\n".join(self.wordList))
QApplication.restoreOverrideCursor()
return QStringListModel(self.words, self.completer)
def stylesheet2(self):
return """
QPlainTextEdit
{
font-family: Helvetica;
font-size: 13px;
background: #E2E2E2;
color: #202020;
border: 1px solid #1EAE3D;
}
QTextEdit
{
background: #292929;
color: #1EAE3D;
font-family: Monospace;
font-size: 8pt;
padding-left: 6px;
border: 1px solid #1EAE3D;
}
QStatusBar
{
font-family: Helvetica;
color: #204a87;
font-size: 8pt;
}
QLabel
{
font-family: Helvetica;
color: #204a87;
font-size: 8pt;
}
QLineEdit
{
font-family: Helvetica;
font-size: 8pt;
}
QPushButton
{
font-family: Helvetica;
font-size: 8pt;
}
QComboBox
{
font-family: Helvetica;
font-size: 8pt;
}
QMenuBar
{
font-family: Helvetica;
font-size: 8pt;
}
QMenu
{
font-family: Helvetica;
font-size: 8pt;
}
QToolBar
{
background: transparent;
}
"""
if __name__ == '__main__':
app = QApplication(argv)
translator = QTranslator(app)
locale = QLocale.system().name()
print(locale)
path = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
print(path)
translator.load('qt_%s' % locale, path)
app.installTranslator(translator)
win = myEditor()
win.setWindowTitle("PyEdit" + "[*]")
win.show()
if len(argv) > 1:
print(argv[1])
win.openFileOnStart(argv[1])
sys.exit(app.exec_())
# syntax.py
import sys
from PyQt5.QtCore import QRegExp
from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter
def format(color, style=''):
'''Return a QTextCharFormat with the given attributes.
'''
_color = QColor()
_color.setNamedColor(color)
_format = QTextCharFormat()
_format.setForeground(_color)
if 'bold' in style:
_format.setFontWeight(QFont.Bold)
if 'italic' in style:
_format.setFontItalic(True)
if 'italicbold' in style:
_format.setFontItalic(True)
_format.setFontWeight(QFont.Bold)
return _format
mybrawn = ("#7E5916")
# Syntax styles that can be shared by all languages
STYLES = {
'keyword': format('#2C2CC8', 'bold'),
'operator': format('darkred'),
'brace': format('darkred'),
'defclass': format('#cc0000', 'bold'),
'classes': format('#cc0000', 'bold'),
'Qtclass': format('black', 'bold'),
'string': format(mybrawn),
'string2': format('#42923b', 'italic'),
'comment': format('#42923b', 'italic'),
'self': format('#D63030', 'italicbold'),
'selfnext': format('#2e3436', 'bold'),
'Qnext': format('#2e3436', 'bold'),
'numbers': format('#C82C2C'),
}
class Highlighter(QSyntaxHighlighter):
'''Syntax highlighter for the Python language.
'''
# Python keywords
keywords = [
'and', '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', 'super', 'try', 'while', 'yield',
'None', 'True', 'False',
]
# Python operators
operators = [
'=',
# Comparison
'==', '!=', '<', '<=', '>', '>=',
# Arithmetic
'\+', '-', '\*', '/', '//', '\%', '\*\*',
# In-place
'\+=', '-=', '\*=', '/=', '\%=',
# Bitwise
'\^', '\|', '\&', '\~', '>>', '<<',
]
# Python braces
braces = [
'\{', '\}', '\(', '\)', '\[', '\]',
]
def __init__(self, document):
QSyntaxHighlighter.__init__(self, document)
tri = ("'''")
trid = ('"""')
# Multi-line strings (expression, flag, style)
# FIXME: The triple-quotes in these two lines will mess up the
# syntax highlighting from this point onward
self.tri_single = (QRegExp(tri), 1, STYLES['string2'])
self.tri_double = (QRegExp(trid), 2, STYLES['string2'])
rules = []
# Keyword, operator, and brace rules
rules += [(r'\b%s\b' % w, 0, STYLES['keyword'])
for w in Highlighter.keywords]
rules += [(r'%s' % o, 0, STYLES['operator'])
for o in Highlighter.operators]
rules += [(r'%s' % b, 0, STYLES['brace'])
for b in Highlighter.braces]
# All other rules
rules += [
# Numeric literals
(r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']),
(r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
(r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']),
# 'self'
(r'\bself\b', 0, STYLES['self']),
# Double-quoted string, possibly containing escape sequences ### "\"([^\"]*)\"" ### "\"(\\w)*\""
(r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
# Single-quoted string, possibly containing escape sequences
(r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']),
# 'def' followed by an word
(r'\bdef\b\s*(\w+)', 1, STYLES['defclass']), ### (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
# 'self.' followed by an word
(r'\bself\b)', 1, STYLES['selfnext']), ### (r'\bself.\b\s*(\w+)', 1, STYLES['selfnext']),
# 'Q' followed by an word
(r'\b[Q.]\b\s*(\w+)', 1, STYLES['Qnext']),
# 'class' followed by an identifier
(r'\bclass\b\s*(\w+)', 1, STYLES['classes']),
# From '#' until a newline
(r'#[^\n]*', 0, STYLES['comment']),
# 'Q' word
#(r'\\bQ[A-Za-z]+\\b', 1, STYLES['Qtclass']), #(QRegExp("\\bQ[A-Za-z]+\\b")
]
# Build a QRegExp for each pattern
self.rules = [(QRegExp(pat), index, fmt)
for (pat, index, fmt) in rules]
def highlightBlock(self, text):
# Apply syntax highlighting to the given block of text.
# Do other syntax formatting
for expression, nth, format in self.rules:
index = expression.indexIn(text, 0)
while index >= 0:
# We actually want the index of the nth match
index = expression.pos(nth)
length = len(expression.cap(nth))
self.setFormat(index, length, format)
index = expression.indexIn(text, index + length)
self.setCurrentBlockState(0)
# Do multi-line strings
in_multiline = self.match_multiline(text, *self.tri_single)
if not in_multiline:
in_multiline = self.match_multiline(text, *self.tri_double)
def match_multiline(self, text, delimiter, in_state, style):
'''Do highlighting of multi-line strings. ``delimiter`` should be a
``QRegExp`` for triple-single-quotes or triple-double-quotes, and
``in_state`` should be a unique integer to represent the corresponding
# syntax.py
import sys
from PyQt5.QtCore import QRegExp
from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter
def format(color, style=''):
'''Return a QTextCharFormat with the given attributes.
'''
_color = QColor()
_color.setNamedColor(color)
_format = QTextCharFormat()
_format.setForeground(_color)
if 'bold' in style:
_format.setFontWeight(QFont.Bold)
if 'italic' in style:
_format.setFontItalic(True)
if 'italicbold' in style:
_format.setFontItalic(True)
_format.setFontWeight(QFont.Bold)
return _format
mybrawn = ("#7E5916")
# Syntax styles that can be shared by all languages
STYLES = {
'keyword': format('#2C2CC8', 'bold'),
'operator': format('darkred'),
'brace': format('darkred'),
'defclass': format('#cc0000', 'bold'),
'classes': format('#cc0000', 'bold'),
'Qtclass': format('black', 'bold'),
'string': format(mybrawn),
'string2': format('#42923b', 'italic'),
'comment': format('#42923b', 'italic'),
'self': format('#D63030', 'italicbold'),
'selfnext': format('#2e3436', 'bold'),
'Qnext': format('#2e3436', 'bold'),
'numbers': format('#C82C2C'),
}
class Highlighter(QSyntaxHighlighter):
'''Syntax highlighter for the Python language.
'''
# Python keywords
keywords = [
'and', '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', 'super', 'try', 'while', 'yield',
'None', 'True', 'False',
]
# Python operators
operators = [
'=',
# Comparison
'==', '!=', '<', '<=', '>', '>=',
# Arithmetic
'\+', '-', '\*', '/', '//', '\%', '\*\*',
# In-place
'\+=', '-=', '\*=', '/=', '\%=',
# Bitwise
'\^', '\|', '\&', '\~', '>>', '<<',
]
# Python braces
braces = [
'\{', '\}', '\(', '\)', '\[', '\]',
]
def __init__(self, document):
QSyntaxHighlighter.__init__(self, document)
tri = ("'''")
trid = ('"""')
# Multi-line strings (expression, flag, style)
# FIXME: The triple-quotes in these two lines will mess up the
# syntax highlighting from this point onward
self.tri_single = (QRegExp(tri), 1, STYLES['string2'])
self.tri_double = (QRegExp(trid), 2, STYLES['string2'])
rules = []
# Keyword, operator, and brace rules
rules += [(r'\b%s\b' % w, 0, STYLES['keyword'])
for w in Highlighter.keywords]
rules += [(r'%s' % o, 0, STYLES['operator'])
for o in Highlighter.operators]
rules += [(r'%s' % b, 0, STYLES['brace'])
for b in Highlighter.braces]
# All other rules
rules += [
# Numeric literals
(r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']),
(r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
(r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']),
# 'self'
(r'\bself\b', 0, STYLES['self']),
# Double-quoted string, possibly containing escape sequences ### "\"([^\"]*)\"" ### "\"(\\w)*\""
(r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
# Single-quoted string, possibly containing escape sequences
(r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']),
# 'def' followed by an word
(r'\bdef\b\s*(\w+)', 1, STYLES['defclass']), ### (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
# 'self.' followed by an word
(r'\bself\b)', 1, STYLES['selfnext']), ### (r'\bself.\b\s*(\w+)', 1, STYLES['selfnext']),
# 'Q' followed by an word
(r'\b[Q.]\b\s*(\w+)', 1, STYLES['Qnext']),
# 'class' followed by an identifier
(r'\bclass\b\s*(\w+)', 1, STYLES['classes']),
# From '#' until a newline
(r'#[^\n]*', 0, STYLES['comment']),
# 'Q' word
#(r'\\bQ[A-Za-z]+\\b', 1, STYLES['Qtclass']), #(QRegExp("\\bQ[A-Za-z]+\\b")
]
# Build a QRegExp for each pattern
self.rules = [(QRegExp(pat), index, fmt)
for (pat, index, fmt) in rules]
def highlightBlock(self, text):
# Apply syntax highlighting to the given block of text.
# Do other syntax formatting
for expression, nth, format in self.rules:
index = expression.indexIn(text, 0)
while index >= 0:
# We actually want the index of the nth match
index = expression.pos(nth)
length = len(expression.cap(nth))
self.setFormat(index, length, format)
index = expression.indexIn(text, index + length)
self.setCurrentBlockState(0)
# Do multi-line strings
in_multiline = self.match_multiline(text, *self.tri_single)
if not in_multiline:
in_multiline = self.match_multiline(text, *self.tri_double)
def match_multiline(self, text, delimiter, in_state, style):
'''Do highlighting of multi-line strings. ``delimiter`` should be a
``QRegExp`` for triple-single-quotes or triple-double-quotes, and
``in_state`` should be a unique integer to represent the corresponding
state changes when inside those strings. Returns True if we're still
inside a multi-line string when this function is finished.
'''
# If inside triple-single quotes, start at 0
if self.previousBlockState() == in_state:
start = 0
add = 0
# Otherwise, look for the delimiter on this line
else:
start = delimiter.indexIn(text)
# Move past this match
add = delimiter.matchedLength()
# As long as there's a delimiter match on this line...
while start >= 0:
# Look for the ending delimiter
end = delimiter.indexIn(text, start + add)
# Ending delimiter on this line?
if end >= add:
length = end - start + add + delimiter.matchedLength()
self.setCurrentBlockState(0)
# No; multi-line string
else:
self.setCurrentBlockState(in_state)
length = len(text) - start + add
# Apply formatting
self.setFormat(start, length, style)
# Look for the next match
start = delimiter.indexIn(text, start + length)
# Return True if still inside a multi-line string, False otherwise
if self.currentBlockState() == in_state:
return True
else:
return False
@Axel-Erfurt
Copy link
Author

Axel-Erfurt commented Dec 19, 2017

tested on Linux only (Ubuntu 16.04)

pyedit_2

@Axel-Erfurt
Copy link
Author

Axel-Erfurt commented Dec 19, 2017

Linux App 64bit

made with pyinstaller

@Axel-Erfurt
Copy link
Author

Axel-Erfurt commented Dec 22, 2017

added Bookmarks
added 'go to line'

added automatic bookmarks on open file

@Axel-Erfurt
Copy link
Author

process output in realtime using QProcess

@Axel-Erfurt
Copy link
Author

Axel-Erfurt commented Dec 26, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment