Merge pull request #310 from vkbo/tidy_formatting

Standard Markdown Text Emphasis
This commit is contained in:
Veronica K. Berglyd Olsen
2020-06-13 21:17:08 +02:00
committed by GitHub
35 changed files with 354 additions and 176 deletions
+6
View File
@@ -2,9 +2,15 @@
## Version 0.9rc1 [2020-xx-xx]
**Core Functionality**
* Underline text formatting has been removed. It is not standard HTML5, not Markdown, and was previously implemented using the double underscore notation that in standard Markdown is renderred as bold text. Instead, novelWriter now renders a single `*` or `-` wrapping a piece of text *within* a paragraphs as italicised text, and a double `**` or `__` as bold text. The keyboard shortcuts and automatic features **only** support the `*` notation. A triple set of `***` are treated as both bold and italicised. PR #310.
* Strikethrough formatting has been added back into novelWriter using the standard Markdown `~~` wrapping. PR #310.
**User Interface**
* The Open Project dialog will now ask before removing an entry from the recent projects list. PR #309.
* The text emphasis functions, either selected from the menu or via keyboard shortcuts, will now try to respond to the command in a more meaningful way. That is, the text editor will try to toggle the bold or italics features independently of eachother on the selected text. A feature to apply both at the same time has also been added. PR #310.
**Other Changes**
+1 -1
View File
@@ -48,7 +48,7 @@ It allows for a minimal set of formatting needed for writing text documents for
These are currently limited to:
* Headings level 1 to 4 using the `#` syntax only.
* Bold, italic and underline text.
* Bold, italic and strikethrough text.
* Hard line breaks using two or more spaces at the end of a line.
That is it.
+10 -5
View File
@@ -33,8 +33,8 @@ Markdown Format
===============
The document editor uses a simplified markdown format.
That is, it supports basic formatting like bold, italics and underline, as well as four levels of headings.
The formats are listed below.
That is, it supports basic formatting like bold, italics and strikethrough, as well as four levels of headings.
The preference of novelWriter is to use `*` for wrapping emphasised text, but `_` is partially supported when typed, but not by the automatic formatting features and keyboard shortcuts.
In addition to these standard markdown features, the editor also allows for comments, that is text that is ignored by the word counter and not exported or, optionally, hidden in the document viewer.
If the first word of a comment is "Synopsis:" (with the colon), the comment is treated specially, and will show up in the Outline View.
@@ -48,9 +48,12 @@ The editor also has a minimal set of keywords used for setting tags and referenc
"``## Title``", "Heading level two. The space after the # is mandatory."
"``### Title``", "Heading level three. The space after the # is mandatory."
"``#### Title``", "Heading level four. The space after the # is mandatory."
"``*text*``", "The text is rendered as italicised text."
"``**text**``", "The text is rendered as bold text."
"``_text_``", "The text is rendered as italicized text."
"``__text__``", "The text is rendered as underlined text."
"``***text***``", "The text is rendered as bold italicised text."
"``_text_``", "Alternative format for italicised text."
"``__text__``", "Alternative format for bold text."
"``~~text~~``", "Strikethrough text."
"``% text...``", "A comment. The text is not exported by default, seen in viewer, or counted towards word counts."
"``% Synopsis: text...``", "A synopsis comment. Shows up in the Synopsis column of the Outline View, but is otherwise treated as a comment."
"``@keyword: value``", "A keyword argument followed by a value, or a comma separated list of values."
@@ -110,6 +113,7 @@ These are as following:
":kbd:`Ctrl-.`", "Correct word under cursor."
":kbd:`Ctrl-,`", "Open the Preferences dialog."
":kbd:`Ctrl-/`", "Change block format to comment."
":kbd:`Ctrl--`", "Strikethrough selected text, or word under cursor."
":kbd:`Ctrl-0`", "Remove block formatting for block under cursor."
":kbd:`Ctrl-1`", "Change block format to header level 1."
":kbd:`Ctrl-2`", "Change block format to header level 2."
@@ -122,7 +126,7 @@ These are as following:
":kbd:`Ctrl-E`", "If in tree view, edit a document or folder settings. (Same as :kbd:`F2`)"
":kbd:`Ctrl-F`", "Open the search bar and search for selected word, if any is selected."
":kbd:`Ctrl-G`", "Find next occurrence of word in current document. (Same as :kbd:`F3`)"
":kbd:`Ctrl-H`", "Open the search and replace bar and search for selected word, if any is selected. (On Mac, this is :kbd:`Cmd+=`)"
":kbd:`Ctrl-H`", "Open the search and replace bar and search for selected word, if any is selected. (On Mac, this is :kbd:`Cmd-=`)"
":kbd:`Ctrl-I`", "Format selected text, or word under cursor, as italic."
":kbd:`Ctrl-N`", "Create new document."
":kbd:`Ctrl-O`", "Open selected document."
@@ -143,6 +147,7 @@ These are as following:
":kbd:`Ctrl-Shift-/`", "Remove block formatting for block under cursor."
":kbd:`Ctrl-Shift-1`", "Replace occurrence of word in current document, and search for next occurrence."
":kbd:`Ctrl-Shift-A`", "Select all text in current paragraph."
":kbd:`Ctrl-Shift-B`", "Format selected text, or word under cursor, as bold and italic."
":kbd:`Ctrl-Shift-D`", "Wrap selected text, or word under cursor, in single quotes."
":kbd:`Ctrl-Shift-G`", "Find previous occurrence of word in current document. (Same as :kbd:`Shift-F3`"
":kbd:`Ctrl-Shift-I`", "Import text to the current document from a text file."
+2 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from nw.constants.iso import isoLanguage, isoCountry
from nw.constants.constants import (
nwConst, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode
nwConst, nwRegEx, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode
)
from nw.constants.enum import (
nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline
@@ -11,6 +11,7 @@ __all__ = [
"isoLanguage",
"isoCountry",
"nwConst",
"nwRegEx",
"nwFiles",
"nwKeyWords",
"nwLabels",
+9
View File
@@ -34,6 +34,15 @@ class nwConst():
# END Class nwConst
class nwRegEx():
FMT_B = r"(?<![\w|\*|_|\\])([\*|_]{2})(?!\s|\*|_)(.+?)(?<![\s|\\])(\1)(?!\w)"
FMT_I = r"(?<![\w|\*|_|\\])([\*|_])(?!\s|\*|_)(.+?)(?<![\s|\\])(\1)(?!\w)"
FMT_BI = r"(?<![\w|\*|\\])([\*]{3})(?!\s|\*)(.+?)(?<![\s|\\])(\1)(?!\w)"
FMT_ST = r"(?<![\w|~|\\])([~]{2})(?!\s|~)(.+?)(?<![\s|\\])(\1)(?!\w)"
# END Class nwRegEx
class nwFiles():
PROJ_FILE = "nwProject.nwx"
+25 -24
View File
@@ -68,30 +68,31 @@ class nwItemLayout(Enum):
class nwDocAction(Enum):
NO_ACTION = 0
UNDO = 1
REDO = 2
CUT = 3
COPY = 4
PASTE = 5
BOLD = 6
ITALIC = 7
U_LINE = 8
S_QUOTE = 9
D_QUOTE = 10
SEL_ALL = 11
SEL_PARA = 12
FIND = 13
REPLACE = 14
GO_NEXT = 15
GO_PREV = 16
REPL_NEXT = 17
BLOCK_H1 = 18
BLOCK_H2 = 19
BLOCK_H3 = 20
BLOCK_H4 = 21
BLOCK_COM = 22
BLOCK_TXT = 23
NO_ACTION = 0
UNDO = 1
REDO = 2
CUT = 3
COPY = 4
PASTE = 5
ITALIC = 6
BOLD = 7
BOLDITALIC = 8
STRIKE = 9
S_QUOTE = 10
D_QUOTE = 11
SEL_ALL = 12
SEL_PARA = 13
FIND = 14
REPLACE = 15
GO_NEXT = 16
GO_PREV = 17
REPL_NEXT = 18
BLOCK_H1 = 19
BLOCK_H2 = 20
BLOCK_H3 = 21
BLOCK_H4 = 22
BLOCK_COM = 23
BLOCK_TXT = 24
# END Enum nwDocAction
+3 -2
View File
@@ -305,11 +305,12 @@ class NWIndex():
elif aLine.startswith(r"%"):
if nTitle > 0:
toCheck = aLine[1:].lstrip().lower()
toCheck = aLine[1:].lstrip()
synTag = toCheck[:9].lower()
tLen = len(aLine)
cLen = len(toCheck)
cOff = tLen - cLen
if toCheck.startswith("synopsis:"):
if synTag == "synopsis:":
self._indexSynopsis(tHandle, isNovel, aLine[cOff+9:].strip(), nTitle)
# Count words for remaining text after last heading
+22 -8
View File
@@ -116,14 +116,28 @@ class ToHtml(Tokenizer):
"""Convert the list of text tokens into a HTML document saved
to theResult.
"""
htmlTags = {
self.FMT_B_B : "<strong>",
self.FMT_B_E : "</strong>",
self.FMT_I_B : "<em>",
self.FMT_I_E : "</em>",
self.FMT_U_B : "<u>",
self.FMT_U_E : "</u>",
}
if self.genMode == self.M_PREVIEW:
htmlTags = { # HTML4 + CSS2
self.FMT_B_B : "<b>",
self.FMT_B_E : "</b>",
self.FMT_I_B : "<i>",
self.FMT_I_E : "</i>",
self.FMT_S_B : "<b><i>",
self.FMT_S_E : "</i></b>",
self.FMT_D_B : "<span style='text-decoration: line-through;'>",
self.FMT_D_E : "</span>",
}
else:
htmlTags = { # HTML5
self.FMT_B_B : "<strong>",
self.FMT_B_E : "</strong>",
self.FMT_I_B : "<em>",
self.FMT_I_E : "</em>",
self.FMT_S_B : "<strong><em>",
self.FMT_S_E : "</em></strong>",
self.FMT_D_B : "<del>",
self.FMT_D_E : "</del>",
}
if self.isNovel and self.genMode != self.M_PREVIEW:
# For novel files for export, we bump the titles one level
+18 -19
View File
@@ -34,7 +34,7 @@ from PyQt5.QtCore import QRegularExpression
from nw.core.document import NWDoc
from nw.core.tools import numberToWord
from nw.constants import nwItemLayout, nwItemType
from nw.constants import nwItemLayout, nwItemType, nwRegEx
logger = logging.getLogger(__name__)
@@ -44,8 +44,10 @@ class Tokenizer():
FMT_B_E = 2 # End bold
FMT_I_B = 3 # Begin italics
FMT_I_E = 4 # End italics
FMT_U_B = 5 # Begin underline
FMT_U_E = 6 # End underline
FMT_S_B = 5 # Begin bold italic
FMT_S_E = 6 # End bold italic
FMT_D_B = 7 # Begin strikeout
FMT_D_E = 8 # End strikeout
T_EMPTY = 1 # Empty line (new paragraph)
T_SYNOPSIS = 2 # Synopsis comment
@@ -292,23 +294,19 @@ class Tokenizer():
The format of the token list is an entry with a four-tuple for
each line in the file. The tuple is as follows:
1: The type of the block, self.T_*
2: The text content of the block, without leading tags
3: The internal formatting map of the text, self.FMT_*
4: The style of the block, self.A_*
2: The line in file where this block occurred
3: The text content of the block, without leading tags
4: The internal formatting map of the text, self.FMT_*
5: The style of the block, self.A_*
"""
# RegExes for adding formatting tags within text lines
# Keep in sync with the DocHighlighter class
rxFormats = [(
QRegularExpression(r"(?<![\w|\\])([\*]{2})(?!\s)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)"),
[None, self.FMT_B_B, None, self.FMT_B_E]
),(
QRegularExpression(r"(?<![\w|_|\\])([_])(?!\s|\1)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)"),
[None, self.FMT_I_B, None, self.FMT_I_E]
),(
QRegularExpression(r"(?<![\w|\\])([_]{2})(?!\s)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)"),
[None, self.FMT_U_B, None, self.FMT_U_E]
)]
rxFormats = [
(QRegularExpression(nwRegEx.FMT_I), [None, self.FMT_I_B, None, self.FMT_I_E]),
(QRegularExpression(nwRegEx.FMT_B), [None, self.FMT_B_B, None, self.FMT_B_E]),
(QRegularExpression(nwRegEx.FMT_BI), [None, self.FMT_S_B, None, self.FMT_S_E]),
(QRegularExpression(nwRegEx.FMT_ST), [None, self.FMT_D_B, None, self.FMT_D_E]),
]
self.theTokens = []
self.theMarkdown = ""
@@ -329,8 +327,9 @@ class Tokenizer():
tmpMarkdown.append("\n")
elif aLine[0] == "%":
cLine = aLine[1:].strip()
if cLine.lower().startswith("synopsis:"):
cLine = aLine[1:].lstrip()
synTag = cLine[:9].lower()
if synTag == "synopsis:":
self.theTokens.append((
self.T_SYNOPSIS,
nLine,
+3 -1
View File
@@ -982,7 +982,9 @@ class GuiBuildNovelDocView(QTextBrowser):
"""
if isinstance(theText, list):
theText = "".join(theText)
theText = theText.replace("&emsp;","&nbsp;"*4)
theText = theText.replace("&emsp;", "&nbsp;"*4)
theText = theText.replace("<del>", "<span style='text-decoration: line-through;'>")
theText = theText.replace("</del>", "</span>")
self.setHtml(theText)
return
+139 -11
View File
@@ -509,16 +509,18 @@ class GuiDocEditor(QTextEdit):
self.copy()
elif theAction == nwDocAction.PASTE:
self.paste()
elif theAction == nwDocAction.BOLD:
self._wrapSelection("**","**")
elif theAction == nwDocAction.ITALIC:
self._wrapSelection("_","_")
elif theAction == nwDocAction.U_LINE:
self._wrapSelection("__","__")
self._toggleEmph(1)
elif theAction == nwDocAction.BOLD:
self._toggleEmph(2)
elif theAction == nwDocAction.BOLDITALIC:
self._toggleEmph(3)
elif theAction == nwDocAction.STRIKE:
self._toggleStrike()
elif theAction == nwDocAction.S_QUOTE:
self._wrapSelection(self.typSQOpen,self.typSQClose)
self._wrapSelection(self.typSQOpen, self.typSQClose)
elif theAction == nwDocAction.D_QUOTE:
self._wrapSelection(self.typDQOpen,self.typDQClose)
self._wrapSelection(self.typDQOpen, self.typDQClose)
elif theAction == nwDocAction.SEL_ALL:
self._makeSelection(QTextCursor.Document)
elif theAction == nwDocAction.SEL_PARA:
@@ -881,18 +883,20 @@ class GuiDocEditor(QTextEdit):
self.bigDoc = False
return
def _wrapSelection(self, tBefore, tAfter):
def _wrapSelection(self, tBefore, tAfter=None):
"""Wraps the selected text in whatever is in tBefore and tAfter.
If there is no selection, the autoSelect setting decides the
action. AutoSelect will select the word under the cursor before
wrapping it. If this feature is disabled, nothing is done.
"""
theCursor = self.textCursor()
if self.mainConf.autoSelect and not theCursor.hasSelection():
theCursor.select(QTextCursor.WordUnderCursor)
if tAfter is None:
tAfter = tBefore
theCursor = self._autoSelect()
if theCursor.hasSelection():
posS = theCursor.selectionStart()
posE = theCursor.selectionEnd()
theCursor.clearSelection()
theCursor.beginEditBlock()
theCursor.setPosition(posE)
@@ -900,10 +904,134 @@ class GuiDocEditor(QTextEdit):
theCursor.setPosition(posS)
theCursor.insertText(tBefore)
theCursor.endEditBlock()
theCursor.setPosition(posE + len(tBefore))
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, posE-posS)
self.setTextCursor(theCursor)
else:
logger.warning("No selection made, nothing to do")
return
def _clearSurrounding(self, theCursor, nChars):
"""Clears n characters before and after the cursor.
"""
if theCursor.hasSelection():
posS = theCursor.selectionStart()
posE = theCursor.selectionEnd()
theCursor.clearSelection()
theCursor.beginEditBlock()
theCursor.setPosition(posS)
for i in range(nChars):
theCursor.deletePreviousChar()
theCursor.setPosition(posE)
for i in range(nChars):
theCursor.deletePreviousChar()
theCursor.endEditBlock()
theCursor.clearSelection()
else:
logger.warning("No selection made, nothing to do")
return
def _autoSelect(self):
"""Returns a cursor which may or may not have a selection based
on user settings and document action.
"""
theCursor = self.textCursor()
if self.mainConf.autoSelect and not theCursor.hasSelection():
theCursor.select(QTextCursor.WordUnderCursor)
self.setTextCursor(theCursor)
return theCursor
def _toggleEmph(self, eLevel):
"""Toggle emphasis of a given level between 1 and 3, where 1 is
italic, 2 is bold, and 3 is bold italic. The current level in
the text is cLevel. The rules are as follows:
cLevel | eLevel | Result
============+============+============
None | Italic | Italic
None | Bold | Bold
None | BoldItalic | BoldItalic
------------+------------+------------
Italic | Italic | None
Italic | Bold | BoldItalic
Italic | BoldItalic | BoldItalic
------------+------------+------------
Bold | Italic | BoldItalic
Bold | Bold | None
Bold | BoldItalic | BoldItalic
------------+------------+------------
BoldItalic | Italic | Bold
BoldItalic | Bold | Italic
BoldItalic | BoldItalic | None
"""
theCursor = self._autoSelect()
if theCursor.hasSelection():
posS = theCursor.selectionStart()
posE = theCursor.selectionEnd()
numB = 0
for n in range(3):
if self.qDocument.characterAt(posS-n-1) == "*":
numB += 1
else:
break
numA = 0
for n in range(3):
if self.qDocument.characterAt(posE+n) == "*":
numA += 1
else:
break
cLevel = min(numB, numA)
if cLevel == 0:
# Has no emphasis, set to desired level
self._wrapSelection("*"*eLevel)
elif cLevel == 3:
# Has max, so reduce by desired level
self._clearSurrounding(theCursor, eLevel)
elif cLevel == eLevel:
# Toggle mode, so clear what we had set
self._clearSurrounding(theCursor, cLevel)
else:
# Already at 1 or 2, increase to 3
self._wrapSelection("*"*(3 - cLevel))
return
def _toggleStrike(self):
"""Toggle strikethrough text.
"""
theCursor = self._autoSelect()
if theCursor.hasSelection():
posS = theCursor.selectionStart()
posE = theCursor.selectionEnd()
numB = 0
for n in range(2):
if self.qDocument.characterAt(posS-n-1) == "~":
numB += 1
else:
break
numA = 0
for n in range(2):
if self.qDocument.characterAt(posE+n) == "~":
numA += 1
else:
break
cLevel = min(numB, numA)
if cLevel == 2:
self._clearSurrounding(theCursor, 2)
else:
self._wrapSelection("~~")
return
def _formatBlock(self, docAction):
"""Changes the block format of the block under the cursor.
"""
+51 -43
View File
@@ -33,7 +33,7 @@ from PyQt5.QtGui import (
QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush
)
from nw.constants import nwUnicode
from nw.constants import nwUnicode, nwRegEx
logger = logging.getLogger(__name__)
@@ -78,7 +78,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"""Initialise the syntax highlighter, setting all the colour
rules and building the regexes.
"""
logger.debug("Setting up highlighting rules")
self.colHead = QColor(*self.theTheme.colHead)
@@ -98,28 +97,28 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.colTrail.setAlpha(64)
self.hStyles = {
"header1" : self._makeFormat(self.colHead, "bold",1.8),
"header2" : self._makeFormat(self.colHead, "bold",1.6),
"header3" : self._makeFormat(self.colHead, "bold",1.4),
"header4" : self._makeFormat(self.colHead, "bold",1.2),
"header1h" : self._makeFormat(self.colHeadH,"bold",1.8),
"header2h" : self._makeFormat(self.colHeadH,"bold",1.6),
"header3h" : self._makeFormat(self.colHeadH,"bold",1.4),
"header4h" : self._makeFormat(self.colHeadH,"bold",1.2),
"bold" : self._makeFormat(self.colEmph, "bold"),
"italic" : self._makeFormat(self.colEmph, "italic"),
"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),
"replace" : self._makeFormat(self.colRepTag),
"hidden" : self._makeFormat(self.colComm),
"keyword" : self._makeFormat(self.colKey),
"modifier" : self._makeFormat(self.colMod),
"value" : self._makeFormat(self.colVal),
"header1" : self._makeFormat(self.colHead, "bold", 1.8),
"header2" : self._makeFormat(self.colHead, "bold", 1.6),
"header3" : self._makeFormat(self.colHead, "bold", 1.4),
"header4" : self._makeFormat(self.colHead, "bold", 1.2),
"header1h" : self._makeFormat(self.colHeadH, "bold", 1.8),
"header2h" : self._makeFormat(self.colHeadH, "bold", 1.6),
"header3h" : self._makeFormat(self.colHeadH, "bold", 1.4),
"header4h" : self._makeFormat(self.colHeadH, "bold", 1.2),
"bold" : self._makeFormat(self.colEmph, "bold"),
"italic" : self._makeFormat(self.colEmph, "italic"),
"bolditalic" : self._makeFormat(self.colEmph, ("bold","italic")),
"strike" : self._makeFormat(self.colEmph, "strike"),
"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),
"replace" : self._makeFormat(self.colRepTag),
"hidden" : self._makeFormat(self.colComm),
"keyword" : self._makeFormat(self.colKey),
"modifier" : self._makeFormat(self.colMod),
"value" : self._makeFormat(self.colVal, "underline"),
}
self.hRules = []
@@ -131,7 +130,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
}
))
# Non-breaking Space
# Non-Breaking Spaces
self.hRules.append((
"[%s]+" % nwUnicode.U_NBSP, {
0 : self.hStyles["nobreak"],
@@ -140,23 +139,30 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# Markdown
self.hRules.append((
r"(?<![\w|\\])([\*]{2})(?!\s)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)", {
1 : self.hStyles["hidden"],
2 : self.hStyles["bold"],
3 : self.hStyles["hidden"],
}
))
self.hRules.append((
r"(?<![\w|_|\\])([_])(?!\s|\1)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)", {
nwRegEx.FMT_I, {
1 : self.hStyles["hidden"],
2 : self.hStyles["italic"],
3 : self.hStyles["hidden"],
}
))
self.hRules.append((
r"(?<![\w|\\])([_]{2})(?!\s)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)", {
nwRegEx.FMT_B, {
1 : self.hStyles["hidden"],
2 : self.hStyles["underline"],
2 : self.hStyles["bold"],
3 : self.hStyles["hidden"],
}
))
self.hRules.append((
nwRegEx.FMT_BI, {
1 : self.hStyles["hidden"],
2 : self.hStyles["bolditalic"],
3 : self.hStyles["hidden"],
}
))
self.hRules.append((
nwRegEx.FMT_ST, {
1 : self.hStyles["hidden"],
2 : self.hStyles["strike"],
3 : self.hStyles["hidden"],
}
))
@@ -196,9 +202,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# Build a QRegExp for spell checker
# Include additional characters that the highlighter should
# consider to be word separators
wordSep = "_+"
wordSep = r"_\+"
wordSep += nwUnicode.U_ENDASH
wordSep += nwUnicode.U_EMDASH
self.spellRx = QRegularExpression("\\b[^\\s%s]+\\b" % wordSep)
self.spellRx = QRegularExpression(r"\b[^\s"+wordSep+r"]+\b")
self.spellRx.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
return True
@@ -233,7 +240,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
def highlightBlock(self, theText):
"""Highlight a single block. Prefer to check first character for
all formats that are defined by their initial characters. This
is significantly faster than running the regex checks we use for
is significantly faster than running the regex checks used for
text paragraphs.
"""
if self.theHandle is None or not theText:
@@ -244,9 +251,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
isValid, theBits, thePos = self.theIndex.scanThis(theText)
isGood = self.theIndex.checkThese(theBits, tItem)
if isValid:
for n in range(len(theBits)):
for n, theBit in enumerate(theBits):
xPos = thePos[n]
xLen = len(theBits[n])
xLen = len(theBit)
if isGood[n]:
if n == 0:
self.setFormat(xPos, xLen, self.hStyles["keyword"])
@@ -279,11 +286,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.setFormat(4, len(theText), self.hStyles["header4"])
elif theText.startswith("%"): # Comments
toCheck = theText[1:].lstrip().lower()
toCheck = theText[1:].lstrip()
synTag = toCheck[:9].lower()
tLen = len(theText)
cLen = len(toCheck)
cOff = tLen - cLen
if toCheck.startswith("synopsis:"):
if synTag == "synopsis:":
self.setFormat(0, cOff+9, self.hStyles["modifier"])
self.setFormat(cOff+9, tLen, self.hStyles["hidden"])
else:
@@ -341,7 +349,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
if "underline" in fmtStyle:
theFormat.setFontUnderline(True)
if "background" in fmtStyle:
theFormat.setBackground(QBrush(fmtCol,Qt.SolidPattern))
theFormat.setBackground(QBrush(fmtCol, Qt.SolidPattern))
if fmtSize is not None:
theFormat.setFontPointSize(round(fmtSize*self.mainConf.textSize))
+19 -12
View File
@@ -515,13 +515,6 @@ class GuiMainMenu(QMenuBar):
# Format
self.fmtMenu = self.addMenu("&Format")
# Format > Bold Text
self.aFmtBold = QAction("Bold Text", self)
self.aFmtBold.setStatusTip("Make selected text bold")
self.aFmtBold.setShortcut("Ctrl+B")
self.aFmtBold.triggered.connect(lambda: self._docAction(nwDocAction.BOLD))
self.fmtMenu.addAction(self.aFmtBold)
# Format > Italic Text
self.aFmtItalic = QAction("Italic Text", self)
self.aFmtItalic.setStatusTip("Make selected text italic")
@@ -529,12 +522,26 @@ class GuiMainMenu(QMenuBar):
self.aFmtItalic.triggered.connect(lambda: self._docAction(nwDocAction.ITALIC))
self.fmtMenu.addAction(self.aFmtItalic)
# Format > Bold Text
self.aFmtBold = QAction("Bold Text", self)
self.aFmtBold.setStatusTip("Make selected text bold")
self.aFmtBold.setShortcut("Ctrl+B")
self.aFmtBold.triggered.connect(lambda: self._docAction(nwDocAction.BOLD))
self.fmtMenu.addAction(self.aFmtBold)
# Format > Underline Text
self.aFmtULine = QAction("Underline Text", self)
self.aFmtULine.setStatusTip("Underline selected text")
self.aFmtULine.setShortcut("Ctrl+U")
self.aFmtULine.triggered.connect(lambda: self._docAction(nwDocAction.U_LINE))
self.fmtMenu.addAction(self.aFmtULine)
self.aFmtBoldIt = QAction("Bold Italic Text", self)
self.aFmtBoldIt.setStatusTip("Make selected text bold and italic")
self.aFmtBoldIt.setShortcut("Ctrl+Shift+B")
self.aFmtBoldIt.triggered.connect(lambda: self._docAction(nwDocAction.BOLDITALIC))
self.fmtMenu.addAction(self.aFmtBoldIt)
# Format > Strikethrough
self.aFmtStrike = QAction("Strikethrough Text", self)
self.aFmtStrike.setStatusTip("Strikethrough selected text")
self.aFmtStrike.setShortcut("Ctrl+-")
self.aFmtStrike.triggered.connect(lambda: self._docAction(nwDocAction.STRIKE))
self.fmtMenu.addAction(self.aFmtStrike)
# Edit > Separator
self.fmtMenu.addSeparator()
+2 -2
View File
@@ -961,9 +961,9 @@ class GuiMain(QMainWindow):
self.addAction(self.mainMenu.aEditPaste)
self.addAction(self.mainMenu.aSelectAll)
self.addAction(self.mainMenu.aSelectPar)
self.addAction(self.mainMenu.aFmtBold)
self.addAction(self.mainMenu.aFmtItalic)
self.addAction(self.mainMenu.aFmtULine)
self.addAction(self.mainMenu.aFmtBold)
self.addAction(self.mainMenu.aFmtBoldIt)
self.addAction(self.mainMenu.aFmtDQuote)
self.addAction(self.mainMenu.aFmtSQuote)
self.addAction(self.mainMenu.aFmtHead1)
+1 -1
View File
@@ -7,7 +7,7 @@
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree.
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and __underscore__.
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, *italic* and ***bold italic***. You can also ~~strike through~~ text.
In addition, the editor supports automatic formatting of “quotes”, both double and single. Depending on the syntax highlighter, these can be in different colours.
-3
View File
@@ -14,6 +14,3 @@ In fact, if you wish, you can add all the scenes in the chapter file too. All no
@location: Earth
This is a second scene in the same file as the previous scene. You can always split the files up later.
+11 -11
View File
@@ -1,21 +1,21 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.9.0rc1" hexVersion="0x000900c1" fileVersion="1.1" timeStamp="2020-06-12 17:13:30">
<novelWriterXML appVersion="0.9.0rc1" hexVersion="0x000900c1" fileVersion="1.1" timeStamp="2020-06-13 10:26:35">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>280</saveCount>
<autoCount>41</autoCount>
<editTime>4312</editTime>
<saveCount>332</saveCount>
<autoCount>56</autoCount>
<editTime>10692</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<spellCheck>True</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>bc0cbd2a407f3</lastEdited>
<lastViewed>b3e74dbc1f584</lastViewed>
<lastWordCount>920</lastWordCount>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastWordCount>927</lastWordCount>
<autoReplace>
<A>B</A>
<B>E</B>
@@ -114,10 +114,10 @@
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>1199</charCount>
<wordCount>216</wordCount>
<charCount>1240</charCount>
<wordCount>223</wordCount>
<paraCount>7</paraCount>
<cursorPos>950</cursorPos>
<cursorPos>403</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>
@@ -129,7 +129,7 @@
<charCount>476</charCount>
<wordCount>93</wordCount>
<paraCount>3</paraCount>
<cursorPos>551</cursorPos>
<cursorPos>428</cursorPos>
</item>
<item handle="ba8a28a246524" order="3" parent="e7ded148d6e4a">
<name>Interlude</name>
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 04468803b92e1:60bdf227455cc:Ancient Europe
%%~ 04468803b92e1:60bdf227455cc:WORLD:NOTE:Ancient Europe
# Ancient Europe
@tag: Europe
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 2426c6f0ca922:6c6afb1247750:Main
%%~ 2426c6f0ca922:6c6afb1247750:PLOT:NOTE:Main
# Main Plot
@tag: Main
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 441420a886d82:6bd935d2490cd:b3643d0f92e32:Chapter Two
%%~ 441420a886d82:6bd935d2490cd:b3643d0f92e32:NOVEL:CHAPTER:Chapter Two
## Chapter Two
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 47666c91c7ccf:6bd935d2490cd:b3643d0f92e32:Scene Five
%%~ 47666c91c7ccf:6bd935d2490cd:b3643d0f92e32:NOVEL:SCENE:Scene Five
### Scene Five
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 4c4f28287af27:67a8707f2f249:Mr. Nobody
%%~ 4c4f28287af27:67a8707f2f249:CHARACTER:NOTE:Mr. Nobody
# Nobody Owens
@tag: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 7a992350f3eb6:b3643d0f92e32:Lorem Ipusm
%%~ 7a992350f3eb6:b3643d0f92e32:NOVEL:TITLE:Lorem Ipusm
# Lorem Ipsum
**By lipsum.com**
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 846352075de7d:b3643d0f92e32:Interlude
%%~ 846352075de7d:b3643d0f92e32:NOVEL:BOOK:Interlude
## Why do we use it?
% Exctracted from the lipsum.com website.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 88243afbe5ed8:45e6b01ca35c1:b3643d0f92e32:Scene One
%%~ 88243afbe5ed8:45e6b01ca35c1:b3643d0f92e32:NOVEL:SCENE:Scene One
### Scene One
@pov: Bod
+2 -2
View File
@@ -1,6 +1,6 @@
%%~ 88d59a277361b:b3643d0f92e32:Prologue
%%~ 88d59a277361b:b3643d0f92e32:NOVEL:UNNUMBERED:Prologue
## Prologue
% Synopsis:Explanation from the lipsum.com website.
_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
*Lorem Ipsum* is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 8c58a65414c23:b3643d0f92e32:Front Matter
%%~ 8c58a65414c23:b3643d0f92e32:NOVEL:PAGE:Front Matter
% Exctracted from the lipsum.com website.
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of “de Finibus Bonorum et Malorum” (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, “Lorem ipsum dolor sit amet..”, comes from a line in section 1.10.32.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ db7e733775d4d:b3643d0f92e32:Act One
%%~ db7e733775d4d:b3643d0f92e32:NOVEL:PARTITION:Act One
# Act One
“Fusce maximus felis libero”
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ eb103bc70c90c:6bd935d2490cd:b3643d0f92e32:Scene Three
%%~ eb103bc70c90c:6bd935d2490cd:b3643d0f92e32:NOVEL:SCENE:Scene Three
### Scene Three
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ f8c0562e50f1b:6bd935d2490cd:b3643d0f92e32:Scene Four
%%~ f8c0562e50f1b:6bd935d2490cd:b3643d0f92e32:NOVEL:SCENE:Scene Four
### Scene Four
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ f96ec11c6a3da:45e6b01ca35c1:b3643d0f92e32:Scene Two
%%~ f96ec11c6a3da:45e6b01ca35c1:b3643d0f92e32:NOVEL:SCENE:Scene Two
### Scene Two
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ fb609cd8319dc:45e6b01ca35c1:b3643d0f92e32:Chapter One
%%~ fb609cd8319dc:45e6b01ca35c1:b3643d0f92e32:NOVEL:CHAPTER:Chapter One
## Chapter One
@pov: Bod
+14 -14
View File
@@ -1,17 +1,20 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="6" autoCount="20" timeStamp="2020-05-30 12:03:01" editTime="1408">
<novelWriterXML appVersion="0.9.0rc1" hexVersion="0x000900c1" fileVersion="1.1" timeStamp="2020-06-13 00:37:49">
<project>
<name>Lorem Ipsum</name>
<title>Lorem Ipsum</title>
<author>lipsum.com</author>
<backup>False</backup>
<saveCount>7</saveCount>
<autoCount>21</autoCount>
<editTime>1459</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<spellCheck>False</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>88d59a277361b</lastEdited>
<lastEdited>04468803b92e1</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>3397</lastWordCount>
<lastWordCount>3847</lastWordCount>
<autoReplace>
<Rep1>Replace Text 1</Rep1>
<Rep2>Replace Text 2</Rep2>
@@ -22,9 +25,6 @@
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<withSynopsis>False</withSynopsis>
<withComments>False</withComments>
<withKeywords>False</withKeywords>
</titleFormat>
<status>
<entry blue="100" green="100" red="100">New</entry>
@@ -81,7 +81,7 @@
<charCount>584</charCount>
<wordCount>92</wordCount>
<paraCount>1</paraCount>
<cursorPos>35</cursorPos>
<cursorPos>79</cursorPos>
</item>
<item handle="db7e733775d4d" order="3" parent="b3643d0f92e32">
<name>Act One</name>
@@ -238,9 +238,9 @@
<status>Main</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>9</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<charCount>1369</charCount>
<wordCount>195</wordCount>
<paraCount>2</paraCount>
<cursorPos>1387</cursorPos>
</item>
<item handle="60bdf227455cc" order="3" parent="None">
@@ -257,9 +257,9 @@
<status>Minor</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>14</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<charCount>1770</charCount>
<wordCount>259</wordCount>
<paraCount>3</paraCount>
<cursorPos>1792</cursorPos>
</item>
</content>
+1 -1
View File
@@ -14,7 +14,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
## Prologue
_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
*Lorem Ipsum* is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
# Act One
+1 -1
View File
@@ -16,7 +16,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
% Synopsis:Explanation from the lipsum.com website.
_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
*Lorem Ipsum* is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
# Act One