Merge pull request #87 from vkbo/non_breaking_space

Non-breaking space support
This commit is contained in:
Veronica K. Berglyd Olsen
2019-10-29 17:43:56 +01:00
committed by GitHub
15 changed files with 185 additions and 64 deletions
+3 -3
View File
@@ -19,7 +19,7 @@ from os import path, mkdir, makedirs, getcwd
from appdirs import user_config_dir
from datetime import datetime
from nw.constants import nwFiles
from nw.constants import nwFiles, nwUnicode
from nw.common import splitVersionNumber
from PyQt5.Qt import PYQT_VERSION_STR
@@ -87,8 +87,8 @@ class Config:
self.doReplaceDots = True
self.wordCountTimer = 5.0
self.fmtSingleQuotes = ["\u2018","\u2019"]
self.fmtDoubleQuotes = ["\u201c","\u201d"]
self.fmtSingleQuotes = [nwUnicode.U_LSQUO,nwUnicode.U_RSQUO]
self.fmtDoubleQuotes = [nwUnicode.U_LDQUO,nwUnicode.U_RDQUO]
self.spellLanguage = "en_GB"
+70 -22
View File
@@ -142,26 +142,74 @@ class nwQuotes():
"\u300e", # Left white corner bracket
"\u300f", # Right white corner bracket
]
HTML = {
"\u0022" : """,
"\u0027" : "'",
"\u00ab" : "«",
"\u00bb" : "»",
"\u2018" : "‘",
"\u2019" : "’",
"\u201a" : "‚",
"\u201b" : "‛",
"\u201c" : "“",
"\u201d" : "”",
"\u201e" : "„",
"\u201f" : "‟",
"\u2039" : "‹",
"\u203a" : "›",
"\u2e42" : "⹂",
"\u300c" : "「",
"\u300d" : "」",
"\u300e" : "『",
"\u300f" : "』",
}
# END Class nwQuotes
# END Class nwQuotes
class nwUnicode:
"""Suppoted unicode character constants and translation maps.
"""
# Unicode Constants
## Quotation Marks
U_QUOT = "\u0022" # Quotation mark
U_APOS = "\u0027" # Apostrophe
U_LAQUO = "\u00ab" # Left-pointing double angle quotation mark
U_RAQUO = "\u00bb" # Right-pointing double angle quotation mark
U_LSQUO = "\u2018" # Left single quotation mark
U_RSQUO = "\u2019" # Right single quotation mark
U_SBQUO = "\u201a" # Single low-9 quotation mark
U_SUQUO = "\u201b" # Single high-reversed-9 quotation mark
U_LDQUO = "\u201c" # Left double quotation mark
U_RDQUO = "\u201d" # Right double quotation mark
U_BDQUO = "\u201e" # Double low-9 quotation mark
U_UDQUO = "\u201f" # Double high-reversed-9 quotation mark
U_LSAQUO = "\u2039" # Single left-pointing angle quotation mark
U_RSAQUO = "\u203a" # Single right-pointing angle quotation mark
U_BDRQUO = "\u2e42" # Double low-reversed-9 quotation mark
U_LCQUO = "\u300c" # Left corner bracket
U_RCQUO = "\u300d" # Right corner bracket
U_LWCQUO = "\u300e" # Left white corner bracket
U_RECQUO = "\u300f" # Right white corner bracket
## Punctuation
U_ENDASH = "\u2013" # Short dash
U_EMDASH = "\u2014" # Long dash
U_HELLIP = "\u2026" # Ellipsis
## Other
U_NBSP = "\u00a0" # Non-breaking space
U_PARA = "\u2029" # Paragraph separator
# HTML Equivalents
## Quotes
H_QUOT = """
H_APOS = "'"
H_LAQUO = "«"
H_RAQUO = "»"
H_LSQUO = "‘"
H_RSQUO = "’"
H_SBQUO = "‚"
H_SUQUO = "‛"
H_LDQUO = "“"
H_RDQUO = "”"
H_BDQUO = "„"
H_UDQUO = "‟"
H_LSAQUO = "‹"
H_RSAQUO = "›"
H_BDRQUO = "⹂"
H_LCQUO = "「"
H_RCQUO = "」"
H_LWCQUO = "『"
H_LWCQUO = "『"
## Punctuation
H_ENDASH = "–"
H_EMDASH = "—"
H_HELLIP = "…"
## Other
H_NBSP = " "
# END Class nwUnicode
+13 -12
View File
@@ -38,18 +38,19 @@ class HtmlFile(TextFile):
self.outFile.write("<!DOCTYPE html>\n")
self.outFile.write("<html>\n")
self.outFile.write("<head>\n")
self.outFile.write("<style>\n")
self.outFile.write(" #page {\n")
self.outFile.write(" margin: 40px auto;\n")
self.outFile.write(" max-width: 769px;\n")
self.outFile.write(" }\n")
self.outFile.write(" .comment {\n")
self.outFile.write(" background-color: #fbfabd;\n")
self.outFile.write(" border: 1px solid #b4b000;\n")
self.outFile.write(" margin: 10px 20px;\n")
self.outFile.write(" padding: 6px;\n")
self.outFile.write(" }\n")
self.outFile.write("</style>\n")
self.outFile.write(" <meta charset='utf-8'>\n")
self.outFile.write(" <style>\n")
self.outFile.write(" #page {\n")
self.outFile.write(" margin: 40px auto;\n")
self.outFile.write(" max-width: 769px;\n")
self.outFile.write(" }\n")
self.outFile.write(" .comment {\n")
self.outFile.write(" background-color: #fbfabd;\n")
self.outFile.write(" border: 1px solid #b4b000;\n")
self.outFile.write(" margin: 10px 20px;\n")
self.outFile.write(" padding: 6px;\n")
self.outFile.write(" }\n")
self.outFile.write(" </style>\n")
self.outFile.write("</head>\n")
self.outFile.write("<body>\n")
self.outFile.write("<article id='page'>\n")
+1
View File
@@ -138,6 +138,7 @@ class TextFile():
self.theConv.tokenizeText()
self.theConv.doHeaders()
self.theConv.doConvert()
self.theConv.doPostProcessing()
if self.theConv.theResult is not None and self.outFile is not None:
self.outFile.write(self.theConv.theResult)
+8 -7
View File
@@ -15,6 +15,7 @@ import re
import nw
from nw.convert.tokenizer import Tokenizer
from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
@@ -28,13 +29,13 @@ class ToHtml(Tokenizer):
Tokenizer.doAutoReplace(self)
repDict = {
"<" : "&lt;",
">" : "&gt;",
"&" : "&amp;",
"\u2013" : "&endash;",
"\u2014" : "$emdash;",
"\u2500" : "$emdash;",
"\u2026" : "&hellip;",
"<" : "&lt;",
">" : "&gt;",
"&" : "&amp;",
nwUnicode.U_ENDASH : nwUnicode.H_ENDASH,
nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
nwUnicode.U_HELLIP : nwUnicode.H_HELLIP,
nwUnicode.U_NBSP : nwUnicode.H_NBSP,
}
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
+15
View File
@@ -16,6 +16,7 @@ import re
import nw
from nw.convert.tokenizer import Tokenizer
from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
@@ -26,6 +27,20 @@ class ToLaTeX(Tokenizer):
self.texCodecFail = False
return
def doPostProcessing(self):
"""The latexcodec misses dashes and non-breaking spaces, so we do those here.
"""
repDict = {
nwUnicode.U_ENDASH : "--",
nwUnicode.U_EMDASH : "---",
nwUnicode.U_NBSP : "~",
}
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self.theResult = xRep.sub(lambda x: repDict[x.group(0)], self.theResult)
return
def doConvert(self):
texTags = {
+12
View File
@@ -16,6 +16,7 @@ import re
import nw
from nw.convert.tokenizer import Tokenizer
from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
@@ -25,6 +26,17 @@ class ToText(Tokenizer):
Tokenizer.__init__(self, theProject, theParent)
return
def doAutoReplace(self):
Tokenizer.doAutoReplace(self)
repDict = {
nwUnicode.U_NBSP : " ",
}
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
return
def doConvert(self):
"""Converts the tokenized text into plain text.
"""
+3
View File
@@ -149,6 +149,9 @@ class Tokenizer():
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
return
def doPostProcessing(self):
return
def tokenizeText(self):
"""Scan the text for either lines starting with specific characters that indicate headers,
comments, commands etc, or just contains plain text. in the case of plain text, apply the
+38 -7
View File
@@ -23,7 +23,7 @@ from nw.project.document import NWDoc
from nw.gui.dochighlight import GuiDocHighlighter
from nw.gui.wordcounter import WordCounter
from nw.tools.spellcheck import NWSpellCheck
from nw.constants import nwFiles
from nw.constants import nwFiles, nwUnicode
from nw.enum import nwDocAction, nwAlert
logger = logging.getLogger(__name__)
@@ -218,7 +218,14 @@ class GuiDocEditor(QTextEdit):
return self.docChanged
def getText(self):
theText = self.toPlainText()
"""Get the text content of the current document. This method uses QTextEdit->toPlainText for
Qt versions lower than 5.9, and the QDocument->toRawText for higher version. The latter
preserves non-breaking spaces, which the former does not.
"""
if self.mainConf.verQtValue >= 50900:
theText = self.qDocument.toRawText().replace(nwUnicode.U_PARA,"\n")
else:
theText = self.toPlainText()
return theText
def setCursorPosition(self, thePosition):
@@ -316,9 +323,26 @@ class GuiDocEditor(QTextEdit):
to know whether we had a selection prior to triggering the _docChange slot, as we do not
want to trigger autoreplace on selections. Autoreplace on selections messes with undo/redo
history.
We also need to intercept the Shift key modifier for certain key combinations that modifies
standard keys like enter and space. However, we don't want to spend a lot of time in this
function as it is triggered on every keypress when typing.
"""
self.hasSelection = self.textCursor().hasSelection()
QTextEdit.keyPressEvent(self, keyEvent)
if keyEvent.modifiers() == Qt.ShiftModifier:
theKey = keyEvent.key()
if theKey == Qt.Key_Return:
self._insertHardBreak()
elif theKey == Qt.Key_Enter:
self._insertHardBreak()
elif theKey == Qt.Key_Space:
self._insertNonBreakingSpace()
else:
QTextEdit.keyPressEvent(self, keyEvent)
else:
QTextEdit.keyPressEvent(self, keyEvent)
return
##
@@ -332,6 +356,13 @@ class GuiDocEditor(QTextEdit):
theCursor.endEditBlock()
return
def _insertNonBreakingSpace(self):
theCursor = self.textCursor()
theCursor.beginEditBlock()
theCursor.insertText(nwUnicode.U_NBSP)
theCursor.endEditBlock()
return
def _openSpellContext(self):
self._openContextMenu(self.cursorRect().center())
return
@@ -442,15 +473,15 @@ class GuiDocEditor(QTextEdit):
elif self.mainConf.doReplaceDash and theTwo == "--":
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
theCursor.insertText("\u2013")
theCursor.insertText(nwUnicode.U_ENDASH)
elif self.mainConf.doReplaceDash and theTwo == "\u2013-":
elif self.mainConf.doReplaceDash and theTwo == nwUnicode.U_ENDASH+"-":
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
theCursor.insertText("\u2014")
theCursor.insertText(nwUnicode.U_EMDASH)
elif self.mainConf.doReplaceDots and theThree == "...":
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 3)
theCursor.insertText("\u2026")
theCursor.insertText(nwUnicode.U_HELLIP)
return
+10
View File
@@ -16,6 +16,8 @@ import nw
from PyQt5.QtCore import Qt, QRegularExpression
from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush
from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
class GuiDocHighlighter(QSyntaxHighlighter):
@@ -87,6 +89,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"strike" : self._makeFormat(self.colEmph, "strike"),
"underline" : self._makeFormat(self.colEmph, "underline"),
"trailing" : self._makeFormat(self.colTrail,"background"),
"nobreak" : self._makeFormat(self.colTrail,"background"),
"dialogue1" : self._makeFormat(self.colDialN),
"dialogue2" : self._makeFormat(self.colDialD),
"dialogue3" : self._makeFormat(self.colDialS),
@@ -145,6 +148,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
}
))
# Non-breaking Space
self.hRules.append((
"[\u00a0]+", {
0 : self.hStyles["nobreak"],
}
))
# Markdown
self.hRules.append((
r"(?<![\w|\\])([\*]{2})(?!\s)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)", {
+1
View File
@@ -106,6 +106,7 @@ class GuiDocViewer(QTextBrowser):
aDoc.doAutoReplace()
aDoc.tokenizeText()
aDoc.doConvert()
aDoc.doPostProcessing()
self.setHtml(aDoc.theResult)
if self.theHandle == tHandle:
self.verticalScrollBar().setValue(sPos)
+2 -1
View File
@@ -100,7 +100,8 @@ class NWDoc():
self.makeAlert(["Could not save document.",str(e)], nwAlert.ERROR)
return False
if path.isfile(docTemp): unlink(docTemp)
if path.isfile(docTemp):
unlink(docTemp)
self.theParent.statusBar.setStatus("Saved Document: %s" % self.theItem.itemName)
@@ -18,4 +18,4 @@ This one is a bit longer. “It also has some dialogue in it” she said, before
Lets auto-replace this A with <A>, and this C with <C>. While <E> is just <E>.
Lets also add some text with a non-breaking space in it, like right here!
@@ -13,6 +13,3 @@ With many cheerful facts about the square of the hypotenuse
With many cheerful facts about the square of the hypotenuse
With many cheerful facts about the square of the hypotenuse
With many cheerful facts about the square of the hypotepotenuse
+8 -8
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.3.2" fileVersion="1.0" timeStamp="2019-10-28 21:42:26">
<novelWriterXML appVersion="0.3.2" fileVersion="1.0" timeStamp="2019-10-29 14:42:48">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
@@ -11,7 +11,7 @@
<spellCheck>True</spellCheck>
<lastEdited>ba8a28a246524</lastEdited>
<lastViewed>ba8a28a246524</lastViewed>
<lastWordCount>859</lastWordCount>
<lastWordCount>873</lastWordCount>
<autoReplace>
<A>B</A>
<B>E</B>
@@ -79,10 +79,10 @@
<status>Notes</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>713</charCount>
<wordCount>132</wordCount>
<paraCount>6</paraCount>
<cursorPos>72</cursorPos>
<charCount>787</charCount>
<wordCount>146</wordCount>
<paraCount>7</paraCount>
<cursorPos>152</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>
@@ -103,10 +103,10 @@
<status>Finished</status>
<expanded>False</expanded>
<layout>UNNUMBERED</layout>
<charCount>626</charCount>
<charCount>630</charCount>
<wordCount>101</wordCount>
<paraCount>3</paraCount>
<cursorPos>583</cursorPos>
<cursorPos>648</cursorPos>
</item>
<item handle="96b68994dfa3d" order="4" parent="e7ded148d6e4a">
<name>A Note on Ipsums</name>