diff --git a/src/EditorOther/ClochurLexer.py b/src/EditorOther/ClochurLexer.py new file mode 100644 index 0000000..1f2374c --- /dev/null +++ b/src/EditorOther/ClochurLexer.py @@ -0,0 +1,161 @@ + +import re +from PyQt5.Qsci import QsciLexerCustom, QsciScintilla +from PyQt5.QtGui import * + + + +class ClochurLexer(QsciLexerCustom): + + def __init__(self, parent=None): + QsciLexerCustom.__init__(self, parent) + self._styles = { + 0: 'Default', + 1: 'Keyword', + 2: 'Comment', + 3: 'String', + 4: 'Rainbow0', + 5: 'Rainbow1', + 6: 'Rainbow2', + 7: 'Rainbow3', + 8: 'Rainbow4', + 9: 'Rainbow5', + 10: 'Rainbow6', + } + + for (k,v) in self._styles.items(): + setattr(self, v, k) + + self.QUOTES = ['"', "'"] + self.PARENTHESIS = ["[", "]"] + + self.PRIMARY = ['define', 'let' , '#t', '#f', 'lambda', '@', 'cond', 'if', 'docu'] + + font = QFont() + font.setFamily(parent.font_family) + font.setPointSize(parent.font_size) + self.setDefaultFont(font) + + # set indent style + self.setAutoIndentStyle(QsciScintilla.AiMaintain) + + def language(self): + return "Clochur" + + def description(self, style): + ret = "Lexer for Clochur - a S-expression-like" + \ + "typesetting Language. %s, %s" % (style, self._styles.get(style, '')) + return ret + + def defaultColor(self, style): + if style == self.Default: + return QColor("#000000") + elif style == self.Keyword: + return QColor("#0000ff") + elif style == self.Comment: + return QColor("#005500") + elif style == self.String: + return QColor("#ce5c00") + elif style == self.Rainbow0: + return QColor("#ff5500") + elif style == self.Rainbow1: + return QColor("#ffaa00") + elif style == self.Rainbow2: + return QColor("#dede00") + elif style == self.Rainbow3: + return QColor("#00ff00") + elif style == self.Rainbow4: + return QColor("#00aaff") + elif style == self.Rainbow5: + return QColor("#0000ff") + elif style == self.Rainbow6: + return QColor("#aa00ff") + + else: + return QsciLexerCustom.defaultColor(self, style) + + + + + def styleText(self, start, end): + editor = self.editor() + if editor is None: + return + + SCI = editor.SendScintilla + set_style = self.setStyling + + source = '' + if end > editor.length(): + end = editor.length() + if end > start: + source = bytearray(end - start) + SCI(QsciScintilla.SCI_GETTEXTRANGE, start, end, source) + if not source: + return + + + self.startStyling(start, 0x1f) + rainbow_state = 0 + + index = SCI(QsciScintilla.SCI_LINEFROMPOSITION, start) + + for line in source.splitlines(True): + #print("%s, %d" % (line, rainbow_state)) + length = len(line) + + i = 0 + + new_state = self.Default + + line_utf8 = line.decode('utf-8') + + split_pattern = re.compile(r'(\s+|\\%|%|\\\[|\\\]|[[]|[]])') + + line_utf8_splitted = split_pattern.split(line_utf8) + + line_utf8_splitted_len_pair = [{"str": item, "len" : len(bytearray(item, "utf-8"))} for item in line_utf8_splitted] + + + #print(line_utf8_splitted_len_pair) + + is_comment = False + + i = 0 + if index > 0: + # pos = SCI(QsciScintilla.SCI_GETLINEENDPOSITION, index - 1) + rainbow_state = SCI(QsciScintilla.SCI_GETLINESTATE, index - 1) + # print(rainbow_state) + + for item in line_utf8_splitted_len_pair: + + '''comment''' + if item["str"] == "%": + is_comment = True + if is_comment == True: + new_state = self.Comment # end of comment + elif item["str"] in self.PRIMARY: # keywords + new_state = self.Keyword + # string + elif re.match(r'^["]([^"]|\\\")*["]$' ,item["str"]) or re.match(r"^[']([^']|\\\')*[']$" ,item["str"]): + new_state = self.String + #parenthesis: rainbow mode + elif item["str"] == "[": + new_state = getattr(self, "Rainbow" + str(rainbow_state)) + rainbow_state = (rainbow_state + 1) % 7 + elif item["str"] == "]": + rainbow_state = (rainbow_state - 1) % 7 + new_state = getattr(self, "Rainbow" + str(rainbow_state)) + else: + pass + + word_length = item["len"] + i += word_length + set_style(word_length, new_state) + + if new_state != self.Comment: + new_state = self.Default + + SCI(QsciScintilla.SCI_SETLINESTATE, index, rainbow_state) + + index += 1 diff --git a/src/EditorOther/CustomQsciEditor.py b/src/EditorOther/CustomQsciEditor.py new file mode 100644 index 0000000..a3ef02b --- /dev/null +++ b/src/EditorOther/CustomQsciEditor.py @@ -0,0 +1,54 @@ +from PyQt5.QtGui import * +from PyQt5.Qsci import QsciScintilla + +from EditorOther.ClochurLexer import ClochurLexer + +class CustomQsciEditor(QsciScintilla): + def __init__(self, parent=None): + super(CustomQsciEditor, self).__init__(parent) + + self.font_family = parent.font_family + self.font_size = parent.font_size + + self.tab_width = 4 + + lexer = ClochurLexer(self) + self.setLexer(lexer) + + # Margin 0 for line numbers + font = QFont() + font.setFamily(self.font_family) + font.setPointSize(self.font_size) + fontMetrics = QFontMetrics(font) + self.setMarginsFont(font) + self.setMarginWidth(0, fontMetrics.width("00") + 6) + self.setMarginLineNumbers(0, True) + self.setMarginsBackgroundColor(QColor("#cccccc")) + + # brace matching + + self.setBraceMatching(QsciScintilla.SloppyBraceMatch) + + # current line color + + self.setCaretLineVisible(True) + self.setCaretLineBackgroundColor(QColor("#fdffce")) + + # set word wrap + self.set_word_wrap() + + # set encoding + self.SendScintilla(QsciScintilla.SCI_SETCODEPAGE, QsciScintilla.SC_CP_UTF8) + self.setUtf8(True) + + # set Auto indent + self.setAutoIndent(True) + self.setIndentationWidth(self.tab_width) + self.setIndentationsUseTabs(False) + self.setTabWidth(4) + self.setBackspaceUnindents(True) + + def set_word_wrap(self): + self.setWrapMode(QsciScintilla.WrapMode.WrapWord) + def set_no_word_wrap(self): + self.setWrapMode(QsciScintilla.WrapMode.WrapNone) diff --git a/src/main.py b/src/main.py index 92c86ef..1b3f5f8 100755 --- a/src/main.py +++ b/src/main.py @@ -3,16 +3,15 @@ import json import os -import re import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5 import QtWebEngineWidgets from PyQt5.QtWidgets import * -from PyQt5.Qsci import QsciScintilla, QsciLexerPython, QsciLexerCustom +from PyQt5.Qsci import QsciScintilla import qrc_resources -from EditorOther import FindReplace +from EditorOther import FindReplace, CustomQsciEditor filename = None @@ -21,10 +20,7 @@ dirname = os.path.abspath(os.path.dirname(__file__)) #os.path.dirname('__file__' PDFJS = os.path.join(dirname, '../thirdparty/pdfjs/web/viewer.html') PDF = os.path.join(dirname, 'example.pdf') -tab_width = 4 -font_family = 'Noto Sans Mono' -font_size = 11 '''Widget for PDF file viewer''' class PDFJSWidget(QtWebEngineWidgets.QWebEngineView): @@ -33,51 +29,7 @@ class PDFJSWidget(QtWebEngineWidgets.QWebEngineView): self.load(QUrl.fromUserInput("file://%s?file=file://%s" % (PDFJS, PDF))) print((dirname,PDFJS, PDF)) -class CustomQsciEditor(QsciScintilla): - def set_word_wrap(self): - self.setWrapMode(QsciScintilla.WrapMode.WrapWord) - def set_no_word_wrap(self): - self.setWrapMode(QsciScintilla.WrapMode.WrapNone) - - def __init__(self, parent=None): - super(CustomQsciEditor, self).__init__(parent) - - lexer = ClochurLexer(self) - self.setLexer(lexer) - - # Margin 0 for line numbers - font = QFont() - font.setFamily(font_family) - font.setPointSize(font_size) - fontMetrics = QFontMetrics(font) - self.setMarginsFont(font) - self.setMarginWidth(0, fontMetrics.width("00") + 6) - self.setMarginLineNumbers(0, True) - self.setMarginsBackgroundColor(QColor("#cccccc")) - - # brace matching - - self.setBraceMatching(QsciScintilla.SloppyBraceMatch) - - # current line color - - self.setCaretLineVisible(True) - self.setCaretLineBackgroundColor(QColor("#fdffce")) - - # set word wrap - self.set_word_wrap() - - # set encoding - self.SendScintilla(QsciScintilla.SCI_SETCODEPAGE, QsciScintilla.SC_CP_UTF8) - self.setUtf8(True) - - # set Auto indent - self.setAutoIndent(True) - self.setIndentationWidth(tab_width) - self.setIndentationsUseTabs(False) - self.setTabWidth(4) - self.setBackspaceUnindents(True) class Window(QMainWindow): @@ -85,6 +37,8 @@ class Window(QMainWindow): super(QMainWindow, self).__init__() self.file = None self.filename = None + self.font_family = 'Noto Sans Mono' + self.font_size = 11 self.tmp_folder = '/tmp' self.tmp_file = 'clochur_tmp.json' @@ -97,6 +51,8 @@ class Window(QMainWindow): self._createEditToolBar() self._createFormatToolBar() + self.setWindowIcon(QIcon(':logo.svg')) + def _createActions(self): self.new_action = QAction(QIcon(":new.svg"), "&New", self) self.new_action.setShortcut('Ctrl+N') @@ -149,6 +105,7 @@ class Window(QMainWindow): self.convert_action = QAction(QIcon(":convert.svg"), "Con&vert", self) self.about_action = QAction("&About", self) + self.about_action.triggered.connect(self.about_call) self.bold_action = QAction(QIcon(":text-bold.svg"), "&Bold", self) self.italic_action = QAction(QIcon(":text-italic.svg"), "&Italic", self) @@ -306,6 +263,13 @@ class Window(QMainWindow): def select_all_call(self): self.editor.selectAll() + + def about_call(self): + about_content = '''A S-expression-like typesetting language powered by SILE engine with a simple text text editor. +http://yoxem.github.com +(c) 2021 Yoxem Chen ''' + + self.about_dialog = QMessageBox.about(self, "About Clochur", about_content) def _createEditToolBar(self): @@ -396,169 +360,17 @@ class Window(QMainWindow): -class ClochurLexer(QsciLexerCustom): - def __init__(self, parent=None): - QsciLexerCustom.__init__(self, parent) - self._styles = { - 0: 'Default', - 1: 'Keyword', - 2: 'Comment', - 3: 'String', - 4: 'Rainbow0', - 5: 'Rainbow1', - 6: 'Rainbow2', - 7: 'Rainbow3', - 8: 'Rainbow4', - 9: 'Rainbow5', - 10: 'Rainbow6', - } - - for (k,v) in self._styles.items(): - setattr(self, v, k) - - self.QUOTES = ['"', "'"] - self.PARENTHESIS = ["[", "]"] - - self.PRIMARY = ['define', 'let' , '#t', '#f', 'lambda', '@', 'cond', 'if', 'docu'] - - font = QFont() - font.setFamily(font_family) - font.setPointSize(font_size) - self.setDefaultFont(font) - - # set indent style - self.setAutoIndentStyle(QsciScintilla.AiMaintain) - - def language(self): - return "Clochur" - - def description(self, style): - ret = "Lexer for Clochur - a S-expression-like" + \ - "typesetting Language. %s, %s" % (style, self._styles.get(style, '')) - return ret - - def defaultColor(self, style): - if style == self.Default: - return QColor("#000000") - elif style == self.Keyword: - return QColor("#0000ff") - elif style == self.Comment: - return QColor("#005500") - elif style == self.String: - return QColor("#ce5c00") - elif style == self.Rainbow0: - return QColor("#ff5500") - elif style == self.Rainbow1: - return QColor("#ffaa00") - elif style == self.Rainbow2: - return QColor("#dede00") - elif style == self.Rainbow3: - return QColor("#00ff00") - elif style == self.Rainbow4: - return QColor("#00aaff") - elif style == self.Rainbow5: - return QColor("#0000ff") - elif style == self.Rainbow6: - return QColor("#aa00ff") - - else: - return QsciLexerCustom.defaultColor(self, style) - - - - - def styleText(self, start, end): - editor = self.editor() - if editor is None: - return - - SCI = editor.SendScintilla - set_style = self.setStyling - - source = '' - if end > editor.length(): - end = editor.length() - if end > start: - source = bytearray(end - start) - SCI(QsciScintilla.SCI_GETTEXTRANGE, start, end, source) - if not source: - return - - - self.startStyling(start, 0x1f) - rainbow_state = 0 - - index = SCI(QsciScintilla.SCI_LINEFROMPOSITION, start) - - for line in source.splitlines(True): - #print("%s, %d" % (line, rainbow_state)) - length = len(line) - - i = 0 - - new_state = self.Default - - line_utf8 = line.decode('utf-8') - - split_pattern = re.compile(r'(\s+|\\%|%|\\\[|\\\]|[[]|[]])') - - line_utf8_splitted = split_pattern.split(line_utf8) - - line_utf8_splitted_len_pair = [{"str": item, "len" : len(bytearray(item, "utf-8"))} for item in line_utf8_splitted] - - - #print(line_utf8_splitted_len_pair) - - is_comment = False - - i = 0 - if index > 0: - # pos = SCI(QsciScintilla.SCI_GETLINEENDPOSITION, index - 1) - rainbow_state = SCI(QsciScintilla.SCI_GETLINESTATE, index - 1) - # print(rainbow_state) - - for item in line_utf8_splitted_len_pair: - - '''comment''' - if item["str"] == "%": - is_comment = True - if is_comment == True: - new_state = self.Comment # end of comment - elif item["str"] in self.PRIMARY: # keywords - new_state = self.Keyword - # string - elif re.match(r'^["]([^"]|\\\")*["]$' ,item["str"]) or re.match(r"^[']([^']|\\\')*[']$" ,item["str"]): - new_state = self.String - #parenthesis: rainbow mode - elif item["str"] == "[": - new_state = getattr(self, "Rainbow" + str(rainbow_state)) - rainbow_state = (rainbow_state + 1) % 7 - elif item["str"] == "]": - rainbow_state = (rainbow_state - 1) % 7 - new_state = getattr(self, "Rainbow" + str(rainbow_state)) - else: - pass - - word_length = item["len"] - i += word_length - set_style(word_length, new_state) - - if new_state != self.Comment: - new_state = self.Default - - SCI(QsciScintilla.SCI_SETLINESTATE, index, rainbow_state) - - index += 1 if __name__ == '__main__': app = QApplication([]) + window = Window() - editor = CustomQsciEditor() + editor = CustomQsciEditor.CustomQsciEditor(window) editor.setMinimumWidth(200) #editor.resize(QSize(500, 2000)) @@ -579,7 +391,6 @@ if __name__ == '__main__': #main_layout.addWidget(pdf_viewer) - window = Window() window.editor = editor untitled_title = window.generate_untitled_title()