@@ -54,7 +54,6 @@ of writing novels. The formatting is currently limited to:
|
||||
* Headings levels 1 to 4 using the `#` syntax only.
|
||||
* Emphasised and strongly emphasised text. These are rendered as italicised and bold text.
|
||||
* Strikethrough text.
|
||||
* Hard line breaks using two or more spaces at the end of a line.
|
||||
|
||||
That is it. Features not supported in the editor are also not exported when using the export tool.
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@ at the same time provide a complete set of features needed for writing a novel.
|
||||
|
||||
.. tip::
|
||||
If you do need to align information in rows and columns in your notes, you can achieve this with
|
||||
tabs and hard line breaks. The tab stop width can be specified in :guilabel:`Preferences` and
|
||||
hard line breaks can be inserted by adding to spaces at the end of the line.
|
||||
tabs and line breaks. The tab stop width can be specified in :guilabel:`Preferences`.
|
||||
|
||||
The main window does not have a toolbar like many other applications do. This reduces clutter, and
|
||||
since the documents are formatted with markdown tags, is more or less redundant. However, all
|
||||
|
||||
@@ -205,15 +205,16 @@ tricky for languages that use the same symbol for these.
|
||||
|
||||
.. _a_ui_md:
|
||||
|
||||
Markdown Format
|
||||
===============
|
||||
The Markdown-Like Format
|
||||
========================
|
||||
|
||||
The document editor uses a simplified markdown format. That is, it supports basic formatting like
|
||||
emphasis (italic), strong importance (bold) and strikethrough text, as well as four levels of
|
||||
headings.
|
||||
The editor itself is a plaintext editor that uses formatting codes for setting meta data values and
|
||||
allowing for some text formatting. The syntax is based on Markdown, but novelWriter is *not* a
|
||||
Markdown editor. It supports basic formatting like emphasis (italic), strong importance (bold)
|
||||
and strikethrough text, as well as four levels of headings.
|
||||
|
||||
Some non-standard markdown features have been added. For instance, novelWriter allows for comments,
|
||||
a synopsis tag, and a set of keyword and value sets used for tags and references.
|
||||
In addition to formatting codes, novelWriter allows for comments, a synopsis tag, and a set of
|
||||
keyword and value sets used for tags and references.
|
||||
|
||||
|
||||
.. _a_ui_md_head:
|
||||
@@ -253,6 +254,33 @@ level of the novel. See :ref:`a_struct_heads` for more details.
|
||||
itself. See :ref:`a_struct_heads_unnum` for more details.
|
||||
|
||||
|
||||
.. _a_ui_md_text:
|
||||
|
||||
Text Paragraphs
|
||||
---------------
|
||||
|
||||
A text paragraph is indicated by a blank line. That is, you need two line breaks to separate two
|
||||
fragments of text into two paragraphs. Single line breaks are treated as line breaks within a
|
||||
paragraph.
|
||||
|
||||
In addition, the editor supports a few additional types of whitespaces.
|
||||
|
||||
* A non-breaking space can be inserted with :kbd:`Ctrl`:kbd:`K`, :kbd:`Space`.
|
||||
* Thin spaces are also supported, and can be inserted with :kbd:`Ctrl`:kbd:`K`,
|
||||
:kbd:`Shift`:kbd:`Space`.
|
||||
* Non-breaking thin space can be inserted with :kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`Space`.
|
||||
|
||||
These are all insert features, and the :guilabel:`Insert` menu has more. They are also listed
|
||||
in :ref:`a_ui_shortcuts_ins`.
|
||||
|
||||
Non-breaking spaces are highlighted by the syntax highlighter with an alternate coloured
|
||||
background, depending on the selected theme.
|
||||
|
||||
.. tip::
|
||||
Non-breaking spaces are the correct type of space to separate a number from its unit. Generally,
|
||||
it prevents the line wrapping algorithms from adding line breaks where it shouldn't.
|
||||
|
||||
|
||||
.. _a_ui_md_emph:
|
||||
|
||||
Text Emphasis
|
||||
@@ -333,34 +361,6 @@ The available tag and reference keywords are listed in the :ref:`a_struct_tags`
|
||||
also be inserted at the cursor position in the editor via the :guilabel:`Insert` menu.
|
||||
|
||||
|
||||
.. _a_ui_md_add:
|
||||
|
||||
Additional Markdown and Non-Standard Features
|
||||
---------------------------------------------
|
||||
|
||||
The editor and viewer also support markdown standard hard line breaks, and preserve non-breaking
|
||||
spaces if running with Qt 5.9 or higher. For older versions, the non-breaking spaces are lost when
|
||||
the document is saved. This is unfortunately hard-coded into the Qt text editor.
|
||||
|
||||
* A hard line break can be achieved by leaving two or more spaces at the end of the line. This is
|
||||
standard markdown syntax. Alternatively, the user can press :kbd:`Ctrl`:kbd:`K`, :kbd:`Return` to
|
||||
insert this type of line break.
|
||||
* A non-breaking space can be inserted with :kbd:`Ctrl`:kbd:`K`, :kbd:`Space`.
|
||||
* Thin spaces are also supported, and can be inserted with :kbd:`Ctrl`:kbd:`K`,
|
||||
:kbd:`Shift`:kbd:`Space`.
|
||||
* Non-breaking thin space can be inserted with :kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`Space`.
|
||||
|
||||
These are all insert features, and the :guilabel:`Insert` menu has more. They are also listed
|
||||
in :ref:`a_ui_shortcuts_ins`.
|
||||
|
||||
Both hard line breaks and non-breaking spaces are highlighted by the syntax highlighter as an
|
||||
alternate coloured background, depending on the selected theme.
|
||||
|
||||
.. tip::
|
||||
Non-breaking spaces are the correct type of space to separate a number from its unit. Generally,
|
||||
it prevents the line wrapping algorithms from adding line breaks where it shouldn't.
|
||||
|
||||
|
||||
.. _a_ui_outline:
|
||||
|
||||
Project Outline View
|
||||
@@ -514,7 +514,6 @@ a key or key combination for the inserted content.
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`.`", "Insert an ellipsis."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`'`", "Insert a prime."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`""`", "Insert a double prime."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`Return`", "Insert a hard line break."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`Space`", "Insert a non-breaking space."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`Shift`:kbd:`Space`", "Insert a thin space."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`Ctrl`:kbd:`Space`", "Insert a thin non-breaking space."
|
||||
|
||||
@@ -20,6 +20,6 @@ hidden = 150, 150, 150
|
||||
keyword = 200, 46, 0
|
||||
value = 184, 200, 0
|
||||
spellcheckline = 200, 46, 0
|
||||
tagerror = 46, 200, 0
|
||||
errorline = 46, 200, 0
|
||||
replacetag = 0, 184, 46
|
||||
modifier = 200, 120, 0
|
||||
|
||||
@@ -20,6 +20,6 @@ hidden = 100, 100, 100
|
||||
keyword = 200, 50, 50
|
||||
value = 50, 150, 50
|
||||
spellcheckline = 200, 0, 0
|
||||
tagerror = 0, 150, 0
|
||||
errorline = 0, 150, 0
|
||||
replacetag = 0, 150, 0
|
||||
modifier = 150, 110, 30
|
||||
|
||||
@@ -20,6 +20,6 @@ hidden = 150, 150, 150
|
||||
keyword = 225, 225, 225
|
||||
value = 200, 200, 200
|
||||
spellcheckline = 200, 46, 0
|
||||
tagerror = 46, 200, 0
|
||||
errorline = 46, 200, 0
|
||||
replacetag = 225, 225, 225
|
||||
modifier = 225, 225, 225
|
||||
|
||||
@@ -20,6 +20,6 @@ hidden = 100, 100, 100
|
||||
keyword = 0, 0, 0
|
||||
value = 20, 20, 20
|
||||
spellcheckline = 200, 0, 0
|
||||
tagerror = 0, 150, 0
|
||||
errorline = 0, 150, 0
|
||||
replacetag = 0, 0, 0
|
||||
modifier = 0, 0, 0
|
||||
|
||||
@@ -40,6 +40,6 @@ hidden = 152, 159, 177
|
||||
keyword = 222, 61, 58
|
||||
value = 150, 74, 193
|
||||
spellcheckline = 222, 61, 58
|
||||
tagerror = 8, 145, 106
|
||||
errorline = 8, 145, 106
|
||||
replacetag = 42, 162, 152
|
||||
modifier = 224, 175, 5
|
||||
|
||||
@@ -40,6 +40,6 @@ hidden = 99, 119, 119
|
||||
keyword = 247, 140, 108
|
||||
value = 199, 146, 234
|
||||
spellcheckline = 247, 140, 108
|
||||
tagerror = 173, 219, 103
|
||||
errorline = 173, 219, 103
|
||||
replacetag = 127, 219, 202
|
||||
modifier = 236, 196, 141
|
||||
|
||||
@@ -20,6 +20,6 @@ hidden = 147, 161, 161
|
||||
keyword = 133, 153, 0
|
||||
value = 203, 75, 22
|
||||
spellcheckline = 203, 75, 22
|
||||
tagerror = 220, 50, 47
|
||||
errorline = 220, 50, 47
|
||||
replacetag = 133, 153, 0
|
||||
modifier = 181, 137, 0
|
||||
|
||||
@@ -20,6 +20,6 @@ hidden = 88, 110, 117
|
||||
keyword = 133, 153, 0
|
||||
value = 203, 75, 22
|
||||
spellcheckline = 203, 75, 22
|
||||
tagerror = 220, 50, 47
|
||||
errorline = 220, 50, 47
|
||||
replacetag = 133, 153, 0
|
||||
modifier = 181, 137, 0
|
||||
|
||||
@@ -40,6 +40,6 @@ hidden = 142, 144, 140
|
||||
keyword = 240, 40, 41
|
||||
value = 137, 89, 168
|
||||
spellcheckline = 240, 40, 41
|
||||
tagerror = 113, 140, 0
|
||||
errorline = 113, 140, 0
|
||||
replacetag = 62, 153, 159
|
||||
modifier = 245, 135, 31
|
||||
|
||||
@@ -40,6 +40,6 @@ hidden = 150, 152, 150
|
||||
keyword = 204, 102, 102
|
||||
value = 178, 148, 187
|
||||
spellcheckline = 204, 102, 102
|
||||
tagerror = 181, 189, 104
|
||||
errorline = 181, 189, 104
|
||||
replacetag = 138, 190, 183
|
||||
modifier = 222, 147, 95
|
||||
|
||||
@@ -40,6 +40,6 @@ hidden = 114, 133, 183
|
||||
keyword = 255, 157, 164
|
||||
value = 235, 187, 255
|
||||
spellcheckline = 255, 157, 164
|
||||
tagerror = 209, 241, 169
|
||||
errorline = 209, 241, 169
|
||||
replacetag = 153, 255, 255
|
||||
modifier = 255, 197, 143
|
||||
|
||||
@@ -40,6 +40,6 @@ hidden = 150, 152, 150
|
||||
keyword = 213, 78, 83
|
||||
value = 195, 151, 216
|
||||
spellcheckline = 213, 78, 83
|
||||
tagerror = 185, 202, 74
|
||||
errorline = 185, 202, 74
|
||||
replacetag = 112, 192, 177
|
||||
modifier = 231, 140, 69
|
||||
|
||||
@@ -40,6 +40,6 @@ hidden = 153, 153, 153
|
||||
keyword = 242, 119, 122
|
||||
value = 204, 153, 204
|
||||
spellcheckline = 242, 119, 122
|
||||
tagerror = 153, 204, 153
|
||||
errorline = 153, 204, 153
|
||||
replacetag = 102, 204, 204
|
||||
modifier = 249, 145, 57
|
||||
|
||||
@@ -138,6 +138,7 @@ class Config:
|
||||
self.doJustify = False # Justify text
|
||||
self.showTabsNSpaces = False # Show tabs and spaces in edior
|
||||
self.showLineEndings = False # Show line endings in editor
|
||||
self.showMultiSpaces = True # Highlight multiple spaces in the text
|
||||
|
||||
self.doReplace = True # Enable auto-replace as you type
|
||||
self.doReplaceSQuote = True # Smart single quotes
|
||||
@@ -602,6 +603,9 @@ class Config:
|
||||
self.showLineEndings = self._parseLine(
|
||||
cnfParse, cnfSec, "showlineendings", self.CNF_BOOL, self.showLineEndings
|
||||
)
|
||||
self.showMultiSpaces = self._parseLine(
|
||||
cnfParse, cnfSec, "showmultispaces", self.CNF_BOOL, self.showMultiSpaces
|
||||
)
|
||||
self.bigDocLimit = self._parseLine(
|
||||
cnfParse, cnfSec, "bigdoclimit", self.CNF_INT, self.bigDocLimit
|
||||
)
|
||||
@@ -765,6 +769,7 @@ class Config:
|
||||
cnfParse.set(cnfSec, "spellcheck", str(self.spellLanguage))
|
||||
cnfParse.set(cnfSec, "showtabsnspaces", str(self.showTabsNSpaces))
|
||||
cnfParse.set(cnfSec, "showlineendings", str(self.showLineEndings))
|
||||
cnfParse.set(cnfSec, "showmultispaces", str(self.showMultiSpaces))
|
||||
cnfParse.set(cnfSec, "bigdoclimit", str(self.bigDocLimit))
|
||||
cnfParse.set(cnfSec, "showfullpath", str(self.showFullPath))
|
||||
cnfParse.set(cnfSec, "highlightquotes", str(self.highlightQuotes))
|
||||
|
||||
+3
-9
@@ -151,7 +151,6 @@ class ToHtml(Tokenizer):
|
||||
thisPar = []
|
||||
parStyle = None
|
||||
tmpResult = []
|
||||
hasHardBreak = False
|
||||
|
||||
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
|
||||
|
||||
@@ -196,16 +195,15 @@ class ToHtml(Tokenizer):
|
||||
if tType == self.T_EMPTY:
|
||||
if parStyle is None:
|
||||
parStyle = ""
|
||||
if hasHardBreak and self.cssStyles:
|
||||
if len(thisPar) > 1 and self.cssStyles:
|
||||
parClass = " class='break'"
|
||||
else:
|
||||
parClass = ""
|
||||
if len(thisPar) > 0:
|
||||
tTemp = "".join(thisPar)
|
||||
tTemp = "<br/>".join(thisPar)
|
||||
tmpResult.append("<p%s%s>%s</p>\n" % (parStyle, parClass, tTemp.rstrip()))
|
||||
thisPar = []
|
||||
parStyle = None
|
||||
hasHardBreak = False
|
||||
|
||||
elif tType == self.T_TITLE:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
@@ -239,11 +237,7 @@ class ToHtml(Tokenizer):
|
||||
parStyle = hStyle
|
||||
for xPos, xLen, xFmt in reversed(tFormat):
|
||||
tTemp = tTemp[:xPos] + htmlTags[xFmt] + tTemp[xPos+xLen:]
|
||||
if tText.endswith(" "):
|
||||
thisPar.append(tTemp.rstrip() + "<br/>")
|
||||
hasHardBreak = True
|
||||
else:
|
||||
thisPar.append(tTemp.rstrip() + " ")
|
||||
thisPar.append(tTemp.rstrip())
|
||||
|
||||
elif tType == self.T_SYNOPSIS and self.doSynopsis:
|
||||
tmpResult.append(self._formatSynopsis(tText))
|
||||
|
||||
+3
-6
@@ -89,12 +89,12 @@ class ToMarkdown(Tokenizer):
|
||||
thisPar = []
|
||||
tmpResult = []
|
||||
|
||||
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
|
||||
for tType, _, tText, tFormat, tStyle in self.theTokens:
|
||||
|
||||
# Process Text Type
|
||||
if tType == self.T_EMPTY:
|
||||
if len(thisPar) > 0:
|
||||
tTemp = "".join(thisPar)
|
||||
tTemp = " \n".join(thisPar)
|
||||
tmpResult.append("%s\n\n" % tTemp.rstrip(" "))
|
||||
thisPar = []
|
||||
|
||||
@@ -128,10 +128,7 @@ class ToMarkdown(Tokenizer):
|
||||
tTemp = tText
|
||||
for xPos, xLen, xFmt in reversed(tFormat):
|
||||
tTemp = tTemp[:xPos] + mdTags[xFmt] + tTemp[xPos+xLen:]
|
||||
if tText.endswith(" "):
|
||||
thisPar.append(tTemp.rstrip() + " \n")
|
||||
else:
|
||||
thisPar.append(tTemp.rstrip() + " ")
|
||||
thisPar.append(tTemp.rstrip())
|
||||
|
||||
elif tType == self.T_SYNOPSIS and self.doSynopsis:
|
||||
tmpResult.append("**%s:** %s\n\n" % (self._localLookup("Synopsis"), tText))
|
||||
|
||||
+8
-16
@@ -327,8 +327,7 @@ class ToOdt(Tokenizer):
|
||||
thisPar = []
|
||||
thisFmt = []
|
||||
parStyle = None
|
||||
hasHardBreak = False
|
||||
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
|
||||
for tType, _, tText, tFormat, tStyle in self.theTokens:
|
||||
|
||||
# Styles
|
||||
oStyle = ODTParagraphStyle()
|
||||
@@ -359,13 +358,13 @@ class ToOdt(Tokenizer):
|
||||
|
||||
# Process Text Types
|
||||
if tType == self.T_EMPTY:
|
||||
if hasHardBreak and parStyle is not None:
|
||||
if len(thisPar) > 1 and parStyle is not None:
|
||||
if self.doJustify:
|
||||
parStyle.setTextAlign("left")
|
||||
|
||||
if len(thisPar) > 0:
|
||||
tTemp = "".join(thisPar)
|
||||
fTemp = "".join(thisFmt)
|
||||
tTemp = "\n".join(thisPar)
|
||||
fTemp = " ".join(thisFmt)
|
||||
tTxt = tTemp.rstrip()
|
||||
tFmt = fTemp[:len(tTxt)]
|
||||
self._addTextPar("Text_Body", parStyle, tTxt, theFmt=tFmt)
|
||||
@@ -373,7 +372,6 @@ class ToOdt(Tokenizer):
|
||||
thisPar = []
|
||||
thisFmt = []
|
||||
parStyle = None
|
||||
hasHardBreak = False
|
||||
|
||||
elif tType == self.T_TITLE:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
@@ -402,23 +400,17 @@ class ToOdt(Tokenizer):
|
||||
self._addTextPar("Text_Body", oStyle, "")
|
||||
|
||||
elif tType == self.T_TEXT:
|
||||
tTemp = tText
|
||||
if parStyle is None:
|
||||
parStyle = oStyle
|
||||
|
||||
tFmt = " "*len(tTemp)
|
||||
tFmt = " "*len(tText)
|
||||
for xPos, xLen, xFmt in tFormat:
|
||||
tFmt = tFmt[:xPos] + odtTags[xFmt] + tFmt[xPos+xLen:]
|
||||
|
||||
tTxt = tTemp.rstrip()
|
||||
tTxt = tText.rstrip()
|
||||
tFmt = tFmt[:len(tTxt)]
|
||||
if tText.endswith(" "):
|
||||
thisPar.append(tTxt + "\n")
|
||||
thisFmt.append(tFmt + " ")
|
||||
hasHardBreak = True
|
||||
else:
|
||||
thisPar.append(tTxt + " ")
|
||||
thisFmt.append(tFmt + " ")
|
||||
thisPar.append(tTxt)
|
||||
thisFmt.append(tFmt)
|
||||
|
||||
elif tType == self.T_SYNOPSIS and self.doSynopsis:
|
||||
tTemp, fTemp = self._formatSynopsis(tText)
|
||||
|
||||
@@ -924,6 +924,19 @@ class GuiPreferencesSyntax(QWidget):
|
||||
self.tr("Applies to emphasis (italic) and strong (bold).")
|
||||
)
|
||||
|
||||
# Text Errors
|
||||
# ===========
|
||||
|
||||
self.mainForm.addGroupLabel(self.tr("Text Errors"))
|
||||
|
||||
self.showMultiSpaces = QSwitch()
|
||||
self.showMultiSpaces.setChecked(self.mainConf.showMultiSpaces)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Mark redundant spaces"),
|
||||
self.showMultiSpaces,
|
||||
self.tr("Trailing spaces or multiple spaces between words.")
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
def saveValues(self):
|
||||
@@ -940,6 +953,9 @@ class GuiPreferencesSyntax(QWidget):
|
||||
# Text Emphasis
|
||||
self.mainConf.highlightEmph = self.highlightEmph.isChecked()
|
||||
|
||||
# Text Errors
|
||||
self.mainConf.showMultiSpaces = self.showMultiSpaces.isChecked()
|
||||
|
||||
self.mainConf.confChanged = True
|
||||
|
||||
return
|
||||
|
||||
+5
-5
@@ -89,17 +89,17 @@ class nwDocAction(Enum):
|
||||
BLOCK_TXT = 18
|
||||
REPL_SNG = 19
|
||||
REPL_DBL = 20
|
||||
RM_BREAKS = 21
|
||||
|
||||
# END Enum nwDocAction
|
||||
|
||||
class nwDocInsert(Enum):
|
||||
|
||||
NO_INSERT = 0
|
||||
HARD_BREAK = 1
|
||||
QUOTE_LS = 2
|
||||
QUOTE_RS = 3
|
||||
QUOTE_LD = 4
|
||||
QUOTE_RD = 5
|
||||
QUOTE_LS = 1
|
||||
QUOTE_RS = 2
|
||||
QUOTE_LD = 3
|
||||
QUOTE_RD = 4
|
||||
|
||||
# END Enum nwDocInsert
|
||||
|
||||
|
||||
+51
-3
@@ -791,6 +791,8 @@ class GuiDocEditor(QTextEdit):
|
||||
self._replaceQuotes("'", self._typSQOpen, self._typSQClose)
|
||||
elif theAction == nwDocAction.REPL_DBL:
|
||||
self._replaceQuotes("\"", self._typDQOpen, self._typDQClose)
|
||||
elif theAction == nwDocAction.RM_BREAKS:
|
||||
self._removeInParLineBreaks()
|
||||
else:
|
||||
logger.debug("Unknown or unsupported document action %s" % str(theAction))
|
||||
self._allowAutoReplace(True)
|
||||
@@ -840,9 +842,7 @@ class GuiDocEditor(QTextEdit):
|
||||
if isinstance(theInsert, str):
|
||||
theText = theInsert
|
||||
elif isinstance(theInsert, nwDocInsert):
|
||||
if theInsert == nwDocInsert.HARD_BREAK:
|
||||
theText = " \n"
|
||||
elif theInsert == nwDocInsert.QUOTE_LS:
|
||||
if theInsert == nwDocInsert.QUOTE_LS:
|
||||
theText = self._typSQOpen
|
||||
elif theInsert == nwDocInsert.QUOTE_RS:
|
||||
theText = self._typSQClose
|
||||
@@ -1787,6 +1787,54 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
return True
|
||||
|
||||
def _removeInParLineBreaks(self):
|
||||
"""Strip line breaks within paragraphs in the selected text.
|
||||
"""
|
||||
theCursor = self.textCursor()
|
||||
theDoc = self.document()
|
||||
|
||||
iS = 0
|
||||
iE = theDoc.blockCount() - 1
|
||||
rS = 0
|
||||
rE = theDoc.characterCount()
|
||||
if theCursor.hasSelection():
|
||||
sBlock = theDoc.findBlock(theCursor.selectionStart())
|
||||
eBlock = theDoc.findBlock(theCursor.selectionEnd())
|
||||
iS = sBlock.blockNumber()
|
||||
iE = eBlock.blockNumber()
|
||||
rS = sBlock.position()
|
||||
rE = eBlock.position() + eBlock.length()
|
||||
|
||||
# Clean up the text
|
||||
currPar = []
|
||||
cleanText = ""
|
||||
for i in range(iS, iE+1):
|
||||
cBlock = theDoc.findBlockByNumber(i)
|
||||
cText = cBlock.text()
|
||||
if cText.strip() == "":
|
||||
if currPar:
|
||||
cleanText += " ".join(currPar) + "\n\n"
|
||||
else:
|
||||
cleanText += "\n"
|
||||
currPar = []
|
||||
elif cText.startswith(("# ", "## ", "### ", "#### ", "@", "%")):
|
||||
cleanText += cText + "\n"
|
||||
else:
|
||||
currPar.append(cText)
|
||||
|
||||
if currPar:
|
||||
cleanText += " ".join(currPar) + "\n\n"
|
||||
|
||||
# Replace the text with the cleaned up text
|
||||
theCursor.beginEditBlock()
|
||||
theCursor.clearSelection()
|
||||
theCursor.setPosition(rS)
|
||||
theCursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, rE-rS)
|
||||
theCursor.insertText(cleanText.rstrip() + "\n")
|
||||
theCursor.endEditBlock()
|
||||
|
||||
return True
|
||||
|
||||
def _makeSelection(self, selMode):
|
||||
"""Wrapper function to select text based on a selection mode.
|
||||
"""
|
||||
|
||||
+18
-14
@@ -71,7 +71,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self.colKey = QColor(0, 0, 0)
|
||||
self.colVal = QColor(0, 0, 0)
|
||||
self.colSpell = QColor(0, 0, 0)
|
||||
self.colTagErr = QColor(0, 0, 0)
|
||||
self.colError = QColor(0, 0, 0)
|
||||
self.colRepTag = QColor(0, 0, 0)
|
||||
|
||||
self.initHighlighter()
|
||||
@@ -95,11 +95,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self.colKey = QColor(*self.theTheme.colKey)
|
||||
self.colVal = QColor(*self.theTheme.colVal)
|
||||
self.colSpell = QColor(*self.theTheme.colSpell)
|
||||
self.colTagErr = QColor(*self.theTheme.colTagErr)
|
||||
self.colError = QColor(*self.theTheme.colError)
|
||||
self.colRepTag = QColor(*self.theTheme.colRepTag)
|
||||
self.colMod = QColor(*self.theTheme.colMod)
|
||||
self.colTrail = QColor(*self.theTheme.colEmph)
|
||||
self.colTrail.setAlpha(64)
|
||||
self.colBreak = QColor(*self.theTheme.colEmph)
|
||||
self.colBreak.setAlpha(64)
|
||||
|
||||
self.colEmph = None
|
||||
if self.mainConf.highlightEmph:
|
||||
@@ -117,8 +117,8 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
"bold" : self._makeFormat(self.colEmph, "bold"),
|
||||
"italic" : self._makeFormat(self.colEmph, "italic"),
|
||||
"strike" : self._makeFormat(self.colHidden, "strike"),
|
||||
"trailing" : self._makeFormat(self.colTrail, "background"),
|
||||
"nobreak" : self._makeFormat(self.colTrail, "background"),
|
||||
"mspaces" : self._makeFormat(self.colError, "errline"),
|
||||
"nobreak" : self._makeFormat(self.colBreak, "background"),
|
||||
"dialogue1" : self._makeFormat(self.colDialN),
|
||||
"dialogue2" : self._makeFormat(self.colDialD),
|
||||
"dialogue3" : self._makeFormat(self.colDialS),
|
||||
@@ -131,12 +131,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
self.hRules = []
|
||||
|
||||
# Trailing Spaces, 2+
|
||||
self.hRules.append((
|
||||
r"[ ]{2,}$", {
|
||||
0 : self.hStyles["trailing"],
|
||||
}
|
||||
))
|
||||
# Multiple or Trailing Spaces
|
||||
if self.mainConf.showMultiSpaces:
|
||||
self.hRules.append((
|
||||
r"[ ]{2,}|[ ]*$", {
|
||||
0 : self.hStyles["mspaces"],
|
||||
}
|
||||
))
|
||||
|
||||
# Non-Breaking Spaces
|
||||
self.hRules.append((
|
||||
@@ -174,7 +175,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
}
|
||||
))
|
||||
|
||||
# Markdown
|
||||
# Markdown Syntax
|
||||
self.hRules.append((
|
||||
nwRegEx.FMT_EI, {
|
||||
1 : self.hStyles["hidden"],
|
||||
@@ -296,7 +297,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self.setFormat(xPos, xLen, self.hStyles["value"])
|
||||
else:
|
||||
kwFmt = self.format(xPos)
|
||||
kwFmt.setUnderlineColor(self.colTagErr)
|
||||
kwFmt.setUnderlineColor(self.colError)
|
||||
kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||
self.setFormat(xPos, xLen, kwFmt)
|
||||
|
||||
@@ -391,6 +392,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
theFormat.setFontItalic(True)
|
||||
if "strike" in fmtStyle:
|
||||
theFormat.setFontStrikeOut(True)
|
||||
if "errline" in fmtStyle:
|
||||
theFormat.setUnderlineColor(self.colError)
|
||||
theFormat.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||
if "underline" in fmtStyle:
|
||||
theFormat.setFontUnderline(True)
|
||||
if "background" in fmtStyle:
|
||||
|
||||
+64
-63
@@ -634,15 +634,8 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aInsDPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_DPRIME))
|
||||
self.mInsPunct.addAction(self.aInsDPrime)
|
||||
|
||||
# Insert > Breaks and Spaces
|
||||
self.mInsBreaks = self.insertMenu.addMenu(self.tr("Breaks and Spaces"))
|
||||
|
||||
# Insert > Hard Line Break
|
||||
self.aInsHardBreak = QAction(self.tr("Hard Line Break"), self)
|
||||
self.aInsHardBreak.setStatusTip(self.tr("Insert a hard line break"))
|
||||
self.aInsHardBreak.setShortcut("Ctrl+K, Return")
|
||||
self.aInsHardBreak.triggered.connect(lambda: self._docInsert(nwDocInsert.HARD_BREAK))
|
||||
self.mInsBreaks.addAction(self.aInsHardBreak)
|
||||
# Insert > White Spaces
|
||||
self.mInsBreaks = self.insertMenu.addMenu(self.tr("White Spaces"))
|
||||
|
||||
# Insert > Non-Breaking Space
|
||||
self.aInsNBSpace = QAction(self.tr("Non-Breaking Space"), self)
|
||||
@@ -750,60 +743,6 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
return
|
||||
|
||||
def _buildSearchMenu(self):
|
||||
"""Assemble the Search menu.
|
||||
"""
|
||||
# Search
|
||||
self.srcMenu = self.addMenu(self.tr("&Search"))
|
||||
|
||||
# Search > Find
|
||||
self.aFind = QAction(self.tr("Find"), self)
|
||||
self.aFind.setStatusTip(self.tr("Find text in document"))
|
||||
self.aFind.setShortcut("Ctrl+F")
|
||||
self.aFind.triggered.connect(lambda: self.theParent.docEditor.beginSearch())
|
||||
self.srcMenu.addAction(self.aFind)
|
||||
|
||||
# Search > Replace
|
||||
self.aReplace = QAction(self.tr("Replace"), self)
|
||||
self.aReplace.setStatusTip(self.tr("Replace text in document"))
|
||||
if self.mainConf.osDarwin:
|
||||
self.aReplace.setShortcut("Ctrl+=")
|
||||
else:
|
||||
self.aReplace.setShortcut("Ctrl+H")
|
||||
self.aReplace.triggered.connect(lambda: self.theParent.docEditor.beginReplace())
|
||||
self.srcMenu.addAction(self.aReplace)
|
||||
|
||||
# Search > Find Next
|
||||
self.aFindNext = QAction(self.tr("Find Next"), self)
|
||||
self.aFindNext.setStatusTip(self.tr("Find next occurrence of text in document"))
|
||||
if self.mainConf.osDarwin:
|
||||
self.aFindNext.setShortcuts(["Ctrl+G", "F3"])
|
||||
else:
|
||||
self.aFindNext.setShortcuts(["F3", "Ctrl+G"])
|
||||
self.aFindNext.triggered.connect(lambda: self.theParent.docEditor.findNext())
|
||||
self.srcMenu.addAction(self.aFindNext)
|
||||
|
||||
# Search > Find Prev
|
||||
self.aFindPrev = QAction(self.tr("Find Previous"), self)
|
||||
self.aFindPrev.setStatusTip(self.tr("Find previous occurrence of text in document"))
|
||||
if self.mainConf.osDarwin:
|
||||
self.aFindPrev.setShortcuts(["Ctrl+Shift+G", "Shift+F3"])
|
||||
else:
|
||||
self.aFindPrev.setShortcuts(["Shift+F3", "Ctrl+Shift+G"])
|
||||
self.aFindPrev.triggered.connect(lambda: self.theParent.docEditor.findNext(goBack=True))
|
||||
self.srcMenu.addAction(self.aFindPrev)
|
||||
|
||||
# Search > Replace Next
|
||||
self.aReplaceNext = QAction(self.tr("Replace Next"), self)
|
||||
self.aReplaceNext.setStatusTip(
|
||||
self.tr("Find and replace next occurrence of text in document")
|
||||
)
|
||||
self.aReplaceNext.setShortcut("Ctrl+Shift+1")
|
||||
self.aReplaceNext.triggered.connect(lambda: self.theParent.docEditor.replaceNext())
|
||||
self.srcMenu.addAction(self.aReplaceNext)
|
||||
|
||||
return
|
||||
|
||||
def _buildFormatMenu(self):
|
||||
"""Assemble the Format menu.
|
||||
"""
|
||||
@@ -912,6 +851,68 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL))
|
||||
self.fmtMenu.addAction(self.aFmtReplDbl)
|
||||
|
||||
# Format > Remove In-Paragraph Breaks
|
||||
self.aFmtRmBreaks = QAction(self.tr("Remove In-Paragraph Breaks"), self)
|
||||
self.aFmtRmBreaks.setStatusTip(
|
||||
self.tr("Removes all line breaks within paragraphs in the selected text")
|
||||
)
|
||||
self.aFmtRmBreaks.triggered.connect(lambda: self._docAction(nwDocAction.RM_BREAKS))
|
||||
self.fmtMenu.addAction(self.aFmtRmBreaks)
|
||||
|
||||
return
|
||||
|
||||
def _buildSearchMenu(self):
|
||||
"""Assemble the Search menu.
|
||||
"""
|
||||
# Search
|
||||
self.srcMenu = self.addMenu(self.tr("&Search"))
|
||||
|
||||
# Search > Find
|
||||
self.aFind = QAction(self.tr("Find"), self)
|
||||
self.aFind.setStatusTip(self.tr("Find text in document"))
|
||||
self.aFind.setShortcut("Ctrl+F")
|
||||
self.aFind.triggered.connect(lambda: self.theParent.docEditor.beginSearch())
|
||||
self.srcMenu.addAction(self.aFind)
|
||||
|
||||
# Search > Replace
|
||||
self.aReplace = QAction(self.tr("Replace"), self)
|
||||
self.aReplace.setStatusTip(self.tr("Replace text in document"))
|
||||
if self.mainConf.osDarwin:
|
||||
self.aReplace.setShortcut("Ctrl+=")
|
||||
else:
|
||||
self.aReplace.setShortcut("Ctrl+H")
|
||||
self.aReplace.triggered.connect(lambda: self.theParent.docEditor.beginReplace())
|
||||
self.srcMenu.addAction(self.aReplace)
|
||||
|
||||
# Search > Find Next
|
||||
self.aFindNext = QAction(self.tr("Find Next"), self)
|
||||
self.aFindNext.setStatusTip(self.tr("Find next occurrence of text in document"))
|
||||
if self.mainConf.osDarwin:
|
||||
self.aFindNext.setShortcuts(["Ctrl+G", "F3"])
|
||||
else:
|
||||
self.aFindNext.setShortcuts(["F3", "Ctrl+G"])
|
||||
self.aFindNext.triggered.connect(lambda: self.theParent.docEditor.findNext())
|
||||
self.srcMenu.addAction(self.aFindNext)
|
||||
|
||||
# Search > Find Prev
|
||||
self.aFindPrev = QAction(self.tr("Find Previous"), self)
|
||||
self.aFindPrev.setStatusTip(self.tr("Find previous occurrence of text in document"))
|
||||
if self.mainConf.osDarwin:
|
||||
self.aFindPrev.setShortcuts(["Ctrl+Shift+G", "Shift+F3"])
|
||||
else:
|
||||
self.aFindPrev.setShortcuts(["Shift+F3", "Ctrl+Shift+G"])
|
||||
self.aFindPrev.triggered.connect(lambda: self.theParent.docEditor.findNext(goBack=True))
|
||||
self.srcMenu.addAction(self.aFindPrev)
|
||||
|
||||
# Search > Replace Next
|
||||
self.aReplaceNext = QAction(self.tr("Replace Next"), self)
|
||||
self.aReplaceNext.setStatusTip(
|
||||
self.tr("Find and replace next occurrence of text in document")
|
||||
)
|
||||
self.aReplaceNext.setShortcut("Ctrl+Shift+1")
|
||||
self.aReplaceNext.triggered.connect(lambda: self.theParent.docEditor.replaceNext())
|
||||
self.srcMenu.addAction(self.aReplaceNext)
|
||||
|
||||
return
|
||||
|
||||
def _buildToolsMenu(self):
|
||||
|
||||
+2
-2
@@ -106,7 +106,7 @@ class GuiTheme:
|
||||
self.colKey = [0, 0, 0]
|
||||
self.colVal = [0, 0, 0]
|
||||
self.colSpell = [0, 0, 0]
|
||||
self.colTagErr = [0, 0, 0]
|
||||
self.colError = [0, 0, 0]
|
||||
self.colRepTag = [0, 0, 0]
|
||||
self.colMod = [0, 0, 0]
|
||||
|
||||
@@ -373,7 +373,7 @@ class GuiTheme:
|
||||
self.colKey = self._loadColour(confParser, cnfSec, "keyword")
|
||||
self.colVal = self._loadColour(confParser, cnfSec, "value")
|
||||
self.colSpell = self._loadColour(confParser, cnfSec, "spellcheckline")
|
||||
self.colTagErr = self._loadColour(confParser, cnfSec, "tagerror")
|
||||
self.colError = self._loadColour(confParser, cnfSec, "errorline")
|
||||
self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag")
|
||||
self.colMod = self._loadColour(confParser, cnfSec, "modifier")
|
||||
|
||||
|
||||
@@ -1318,7 +1318,6 @@ class GuiMain(QMainWindow):
|
||||
self.addAction(self.mainMenu.aInsEllipsis)
|
||||
self.addAction(self.mainMenu.aInsPrime)
|
||||
self.addAction(self.mainMenu.aInsDPrime)
|
||||
self.addAction(self.mainMenu.aInsHardBreak)
|
||||
self.addAction(self.mainMenu.aInsNBSpace)
|
||||
self.addAction(self.mainMenu.aInsThinSpace)
|
||||
self.addAction(self.mainMenu.aInsThinNBSpace)
|
||||
|
||||
@@ -55,6 +55,7 @@ spelltool = internal
|
||||
spellcheck = en
|
||||
showtabsnspaces = False
|
||||
showlineendings = False
|
||||
showmultispaces = True
|
||||
bigdoclimit = 800
|
||||
showfullpath = True
|
||||
highlightquotes = True
|
||||
|
||||
@@ -55,6 +55,7 @@ spelltool = internal
|
||||
spellcheck = en
|
||||
showtabsnspaces = True
|
||||
showlineendings = True
|
||||
showmultispaces = True
|
||||
bigdoclimit = 500
|
||||
showfullpath = False
|
||||
highlightquotes = False
|
||||
|
||||
@@ -93,7 +93,8 @@ def testCoreToOdt_Convert(dummyGUI):
|
||||
'<text:span text:style-name="T2">bold</text:span>'
|
||||
'<text:span text:style-name="T1"> and </text:span>'
|
||||
'<text:span text:style-name="T3">italics</text:span>'
|
||||
'<text:span text:style-name="T1"> text</text:span> text. No format</text:p>'
|
||||
'<text:span text:style-name="T1"> text</text:span> text.'
|
||||
'<text:line-break/>No format</text:p>'
|
||||
'</office:text>'
|
||||
)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDir):
|
||||
def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
|
||||
"""Test the document editor.
|
||||
"""
|
||||
# Block message box
|
||||
|
||||
@@ -236,6 +236,47 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
"Also text with “double” quotes which are “less tricky”.\n\n"
|
||||
)
|
||||
|
||||
# Remove in-paragraph line breaks
|
||||
nwGUI.docEditor.setText((
|
||||
"### New Text\n\n"
|
||||
"@char: Someone\n"
|
||||
"@location: Somewhere\n\n"
|
||||
"% Some comment ...\n\n"
|
||||
"Here is some text\non multiple\nlines.\n\n"
|
||||
"With another paragraph\nhere."
|
||||
))
|
||||
nwGUI.mainMenu.aFmtRmBreaks.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText() == (
|
||||
"### New Text\n\n"
|
||||
"@char: Someone\n"
|
||||
"@location: Somewhere\n\n"
|
||||
"% Some comment ...\n\n"
|
||||
"Here is some text on multiple lines.\n\n"
|
||||
"With another paragraph here.\n"
|
||||
)
|
||||
|
||||
nwGUI.docEditor.setText((
|
||||
"### New Text\n\n"
|
||||
"@char: Someone\n"
|
||||
"@location: Somewhere\n\n"
|
||||
"% Some comment ...\n\n"
|
||||
"Here is some text\non multiple\nlines.\n\n"
|
||||
"With another paragraph\nhere."
|
||||
))
|
||||
theCursor = nwGUI.docEditor.textCursor()
|
||||
theCursor.setPosition(74)
|
||||
theCursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, 29)
|
||||
nwGUI.docEditor.setTextCursor(theCursor)
|
||||
nwGUI.mainMenu.aFmtRmBreaks.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText() == (
|
||||
"### New Text\n\n"
|
||||
"@char: Someone\n"
|
||||
"@location: Somewhere\n\n"
|
||||
"% Some comment ...\n\n"
|
||||
"Here is some text on multiple lines.\n\n"
|
||||
"With another paragraph\nhere."
|
||||
)
|
||||
|
||||
# Test Invalid Document Action
|
||||
assert not nwGUI.docEditor.docAction(nwDocAction.NO_ACTION)
|
||||
|
||||
@@ -467,10 +508,6 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert nwGUI.docEditor.getText() == nwUnicode.U_DIVIDE
|
||||
nwGUI.docEditor.clear()
|
||||
|
||||
nwGUI.mainMenu.aInsHardBreak.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText() == " \n"
|
||||
nwGUI.docEditor.clear()
|
||||
|
||||
nwGUI.mainMenu.aInsNBSpace.activate(QAction.Trigger)
|
||||
if nwGUI.mainConf.verQtValue >= 50900:
|
||||
assert nwGUI.docEditor.getText() == nwUnicode.U_NBSP
|
||||
|
||||
@@ -109,7 +109,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
|
||||
assert nwGUI.theTheme.colKey == [242, 119, 122]
|
||||
assert nwGUI.theTheme.colVal == [204, 153, 204]
|
||||
assert nwGUI.theTheme.colSpell == [242, 119, 122]
|
||||
assert nwGUI.theTheme.colTagErr == [153, 204, 153]
|
||||
assert nwGUI.theTheme.colError == [153, 204, 153]
|
||||
assert nwGUI.theTheme.colRepTag == [102, 204, 204]
|
||||
assert nwGUI.theTheme.colMod == [249, 145, 57]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user