From aee91ef92594e715a024e8b98c258b8a3e22d526 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" Date: Sat, 6 Apr 2019 16:50:42 +0200 Subject: [PATCH] Added editor highlighting for markdown. --- nw/gui/doceditor.py | 45 +++++--- nw/gui/dochighlight.py | 108 ++++++++++++++++++ .../sampleNovel/data_6/36b6aa9b697b_main.md | 14 ++- .../sampleNovel/data_9/6b68994dfa3d_main.md | 10 +- 4 files changed, 155 insertions(+), 22 deletions(-) create mode 100644 nw/gui/dochighlight.py diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 85d9802d..76824615 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -13,9 +13,11 @@ import logging import nw -from PyQt5.QtWidgets import QWidget, QTextEdit, QHBoxLayout, QVBoxLayout, QFrame, QSplitter, QToolBar, QAction, QScrollArea -from PyQt5.QtCore import Qt, QSize, QSizeF -from PyQt5.QtGui import QIcon, QFont +from PyQt5.QtWidgets import QWidget, QTextEdit, QHBoxLayout, QVBoxLayout, QFrame, QSplitter, QToolBar, QAction, QScrollArea +from PyQt5.QtCore import Qt, QSize, QSizeF +from PyQt5.QtGui import QIcon, QFont, QTextCursor, QTextFormat, QTextBlockFormat + +from nw.gui.dochighlight import GuiDocHighlighter logger = logging.getLogger(__name__) @@ -31,6 +33,8 @@ class GuiDocEditor(QWidget): self.guiEditor = QTextEdit() self.guiEditor.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.hLight = GuiDocHighlighter(self.guiEditor.document()) + self.setLayout(self.outerBox) self.metaPane = QFrame() @@ -55,29 +59,32 @@ class GuiDocEditor(QWidget): # self.guiEditor.setFixedWidth(600) self.guiEditor.setMinimumWidth(600) + self.guiEditor.setAcceptRichText(False) # self.guiEditor.setCurrentFont(self.docFont) # self.guiEditor.setContentsMargins(20,20,20,20) - self.guiEditor.setStyleSheet( - "QTextEdit {background-color: #ffffff;};" - ) + self.guiEditor.setStyleSheet(""" + QTextEdit { + background-color: #ffffff; + } + """) theDoc = self.guiEditor.document() theDoc.setDefaultFont(QFont("Source Sans Pro",13)) theDoc.setDocumentMargin(20) theDoc.setTextWidth(600) # theDoc.setPageSize(QSizeF(600,600)) - theDoc.setDefaultStyleSheet(""" - body { - background-color: #ffffff; - } - p { - text-indent: 0px; - margin: 4px 0px; - text-align: justify; - } - p+p { - text-indent: 40px; - } - """) + # theDoc.setDefaultStyleSheet(""" + # body { + # background-color: #ffffff; + # } + # p { + # text-indent: 0px; + # margin: 4px 0px; + # text-align: justify; + # } + # p+p { + # text-indent: 40px; + # } + # """) logger.debug("DocEditor initialisation complete") diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py new file mode 100644 index 00000000..b2e09ffb --- /dev/null +++ b/nw/gui/dochighlight.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -* +"""novelWriter GUI Document Highlighter + + novelWriter – GUI Document Highlighter +======================================== + Syntax highlighting for MarkDown + + File History: + Created: 2019-04-06 [0.0.1] + +""" + +import logging +import nw + +from PyQt5.QtCore import QRegularExpression +from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter + +logger = logging.getLogger(__name__) + +class GuiDocHighlighter(QSyntaxHighlighter): + + def __init__(self, theDoc): + QSyntaxHighlighter.__init__(self, theDoc) + + logger.debug("Initialising DocHighlighter ...") + self.mainConf = nw.CONFIG + self.theDoc = theDoc + + self.hStyles = { + "header1" : self._makeFormat(( 0, 0,200,255),"bold",20), + "header2" : self._makeFormat(( 0, 0,200,255),"bold",18), + "header3" : self._makeFormat(( 0, 0,200,255),"bold",16), + "header4" : self._makeFormat(( 0, 0,200,255),"bold",14), + "bold" : self._makeFormat((192,150, 0,255),"bold"), + "italic" : self._makeFormat(( 0, 0, 0,255),"italic"), + "strike" : self._makeFormat(( 0, 0, 0,255),"strike"), + "underline" : self._makeFormat(( 0, 0, 0,255),"underline"), + "dialogue" : self._makeFormat(( 0,150, 0,255)), + "hidden" : self._makeFormat((100,100,100,255)), + "keyword" : self._makeFormat((192,150, 0,255)), + "value" : self._makeFormat(( 0,150, 0,255)), + } + + self.hRules = [ + (r"^#{1}[^#].*[^\n]", 0, self.hStyles["header1"]), + (r"^#{2}[^#].*[^\n]", 0, self.hStyles["header2"]), + (r"^#{3}[^#].*[^\n]", 0, self.hStyles["header3"]), + (r"^#{4}[^#].*[^\n]", 0, self.hStyles["header4"]), + (r"\*{2}(.+?)\*{2}", 0, self.hStyles["bold"]), + (r"\/{2}(.+?)\/{2}", 0, self.hStyles["italic"]), + (r"~{2}(.+?)~{2}", 0, self.hStyles["strike"]), + (r"_{2}(.+?)_{2}", 0, self.hStyles["underline"]), + (r'"(.+?)"', 0, self.hStyles["dialogue"]), + (r"«(.+?)»", 0, self.hStyles["dialogue"]), + (r"“(.+?)”", 0, self.hStyles["dialogue"]), + (r"^(@.+?)\s*:\s*(.+?)$", 1, self.hStyles["keyword"]), + (r"^(@.+?)\s*:\s*(.+?)$", 2, self.hStyles["value"]), + (r"^%.*$", 0, self.hStyles["hidden"]), + ] + + # Build a QRegExp for each pattern + self.rules = [(QRegularExpression(a),b,c) for (a,b,c) in self.hRules] + + logger.debug("DocHighlighter initialisation complete") + + return + + def _makeFormat(self, fmtCol=None, fmtStyle=None, fmtSize=None): + theFormat = QTextCharFormat() + + if fmtCol is not None: + theCol = QColor() + if isinstance(fmtCol,str): + theCol.setNamedColor(fmtCol) + else: + theCol.setRgb(*fmtCol) + theFormat.setForeground(theCol) + + if fmtStyle is not None: + if "bold" in fmtStyle: + theFormat.setFontWeight(QFont.Bold) + if "italic" in fmtStyle: + theFormat.setFontItalic(True) + if "strike" in fmtStyle: + theFormat.setFontStrikeOut(True) + if "underline" in fmtStyle: + theFormat.setFontUnderline(True) + + if isinstance(fmtSize,int): + theFormat.setFontPointSize(fmtSize) + + return theFormat + + def highlightBlock(self, theText): + for rX, nth, fmt in self.rules: + rxItt = rX.globalMatch(theText, 0) + while rxItt.hasNext(): + rxMatch = rxItt.next() + xPos = rxMatch.capturedStart(nth) + xLen = rxMatch.capturedLength(nth) + # logger.verbose("Captured[%d]: '%s'" % (nth,rxMatch.captured(nth))) + # print(rxMatch.capturedTexts()) + self.setFormat(xPos, xLen, fmt) + + self.setCurrentBlockState(0) + +# END Class DocHighlighter diff --git a/sample/sampleNovel/data_6/36b6aa9b697b_main.md b/sample/sampleNovel/data_6/36b6aa9b697b_main.md index 87b2fb17..b30f5024 100644 --- a/sample/sampleNovel/data_6/36b6aa9b697b_main.md +++ b/sample/sampleNovel/data_6/36b6aa9b697b_main.md @@ -1 +1,13 @@ -File with stuff. \ No newline at end of file +# This is the Title + +## ... and this is a subtitle + +% Begin Meta +@POV: Sam +@Chars: Sam, Adam, Scott +% End Meta + +Some text here would look good as well, and maybe some "dialogue"? + +**Some Section** + diff --git a/sample/sampleNovel/data_9/6b68994dfa3d_main.md b/sample/sampleNovel/data_9/6b68994dfa3d_main.md index 69a1d161..ebf9539a 100644 --- a/sample/sampleNovel/data_9/6b68994dfa3d_main.md +++ b/sample/sampleNovel/data_9/6b68994dfa3d_main.md @@ -1,8 +1,14 @@ # Lorem Ipsum -**Lorem** ipsum dolor sit amet, consectetur adipiscing elit. Proin vitae nisi augue. Sed elementum lacus risus, eu lobortis ipsum malesuada rutrum. Nam egestas elit id commodo porta. Ut eget lacinia ipsum, non interdum mi. Sed tincidunt commodo metus, at faucibus nisi finibus eget. In malesuada egestas nulla et tempor. Nulla eu sem vel mi sollicitudin cursus. Nullam ultricies ex eu dui scelerisque, ut venenatis turpis ultricies. +## Header 2 -Nunc in ex molestie, efficitur diam semper, pellentesque eros. Morbi vel lacus quis turpis sagittis iaculis. Sed rhoncus tortor et finibus lobortis. Suspendisse non ultrices odio, at venenatis mi. Sed congue congue tortor, et egestas neque malesuada ac. Nulla facilisi. Maecenas finibus elementum vestibulum. Nam id rhoncus mauris, vel ullamcorper neque. +### Header 3 + +#### Header 4 + +**Lorem** ipsum dolor sit amet, consectetur "adipiscing" elit. Proin vitae nisi augue. Sed elementum lacus risus, eu lobortis ipsum malesuada rutrum. Nam egestas elit id commodo porta. **Ut eget lacinia ipsum, non interdum mi.** Sed tincidunt commodo metus, at faucibus nisi finibus eget. In malesuada egestas nulla et tempor. //Nulla eu sem// vel mi sollicitudin cursus. Nullam ultricies ex eu dui scelerisque, ut venenatis turpis ultricies. + +Nunc in ex molestie, __efficitur__ diam ~~semper~~, pellentesque eros. Morbi vel lacus quis turpis sagittis iaculis. Sed rhoncus tortor et finibus lobortis. Suspendisse non ultrices odio, at venenatis mi. Sed congue congue tortor, et egestas neque malesuada ac. Nulla facilisi. Maecenas finibus elementum vestibulum. Nam id rhoncus mauris, vel “ullamcorper” «neque». Quisque mattis magna purus, vel ultricies sem feugiat pellentesque. Nam mi nisl, consectetur vel facilisis nec, condimentum at libero. Sed dolor lacus, fermentum vel nisi sed, auctor pretium urna. Nulla accumsan arcu ut ligula faucibus, non facilisis mi tempor. Nam consectetur dui dapibus lectus cursus, sed laoreet nisl maximus. Maecenas eleifend sapien eu porttitor placerat. Maecenas quis massa non tortor imperdiet porta. Nunc non tristique magna. Vivamus ornare quam convallis metus vestibulum, id molestie quam pretium. Maecenas tincidunt tincidunt tortor lobortis suscipit. Vivamus tempor dolor quis auctor sollicitudin. Integer imperdiet urna dui, at vehicula dui consequat vitae. Donec aliquet feugiat tellus sed dignissim. Nulla pretium libero ut elit porta sagittis. Praesent commodo vulputate lacinia.