Fix merge conflicts

This commit is contained in:
Veronica K. B. Olsen
2019-11-20 18:56:18 +01:00
17 changed files with 197 additions and 85 deletions
+11
View File
@@ -1,5 +1,16 @@
# novelWriter ChangeLog
## Version 0.4.2 [2019-11-17]
**User Interface**
* Distraction free mode now also hides the menu bar, but all keyboard shortcuts used for editing remain active. The rest are disabled. PR #142.
**Bug Fixes**
* Fixed various issues with spell checking highlighting. The highlighting and the editor didn't always agree on what words were spelled wrong. PR #141.
* The status bar now shows what spell checking language is actually loaded. Previously, it just showed the language selected in the settings. That was a bit misleading as the available dictionaries can change due to the change in installed dictionary on the system. PR #145.
## Version 0.4.1 [2019-11-10]
**Features**
+29 -1
View File
@@ -4,7 +4,7 @@
[![codecov](https://codecov.io/gh/vkbo/novelWriter/branch/master/graph/badge.svg)](https://codecov.io/gh/vkbo/novelWriter)
[![Documentation Status](https://readthedocs.org/projects/novelwriter/badge/?version=latest)](https://novelwriter.readthedocs.io/en/latest/?badge=latest)
novelWriter is a markdown-like text editor designed fro writing novels and larger projects of many smaller plain text documents.
novelWriter is a markdown-like text editor designed for writing novels and larger projects of many smaller plain text documents.
The documentation is available here: [novelwriter.readthedocs.io](https://novelwriter.readthedocs.io/).
@@ -20,6 +20,34 @@ If you do use it for real projects, please run backups frequently to avoid data
There is a built in backup feature that can pack the entire project into a zip file on close.
Please check the documentation for further details.
## Markdown Flavour
novelWriter is **not** a full-feature Markdown editor.
It allows for a minimal set of formatting needed for writing text documents for novels.
These are currently limited to:
* Headings level 1 to 4 using the `#` syntax only.
* Bold, italic and underline 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.
In addition, novelWriter adds the following, which is otherwise not supported by Markdown:
* A line starting with `%` is treated as a comment and not rendered on exports unless requested.
Comments do not count towards the word count.
* A set of meta data keyword/value sets starting with the character `@`.
This is used for tagging and inter-linking documents.
* Non-breaking spaces are supported as long as your system is using at least Qt 5.9.
For earlier version, non-breaking spaces are converted to normal spaces when saving the document.
This is done by the Qt library.
* Tabs may be rendered, depending on export format.
The core export format that should render properly all supported features is the HTML export.
This format also forms the basis of conversion to Office type document formats with Pandoc.
Note that Pandoc itself strips some formatting from the document during conversion, so the final result may be different than expected.
## Implementation
The application is written in Python3 using Qt5 via PyQt5.
+2 -2
View File
@@ -24,9 +24,9 @@ copyright = "2018-2019, Veronica Berglyd Olsen"
author = "Veronica Berglyd Olsen"
# The short X.Y version
version = "0.4.1"
version = "0.4.2"
# The full version, including alpha/beta/rc tags
release = "0.4.1"
release = "0.4.2"
# -- General configuration ---------------------------------------------------
+2 -2
View File
@@ -25,8 +25,8 @@ __package__ = "novelWriter"
__author__ = "Veronica Berglyd Olsen"
__copyright__ = "Copyright 20182019, Veronica Berglyd Olsen"
__license__ = "GPLv3"
__version__ = "0.4.1"
__date__ = "2019-11-10"
__version__ = "0.4.2"
__date__ = "2019-11-17"
__maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net"
__status__ = "Development"
+5
View File
@@ -93,6 +93,7 @@ class Config:
self.wordCountTimer = 5.0
self.showTabsNSpaces = False
self.showLineEndings = False
self.bigDocLimit = 800
self.fmtApostrophe = nwUnicode.U_RSQUO
self.fmtSingleQuotes = [nwUnicode.U_LSQUO,nwUnicode.U_RSQUO]
@@ -322,6 +323,9 @@ class Config:
self.showLineEndings = self._parseLine(
cnfParse, cnfSec, "showlineendings", self.CNF_BOOL, self.showLineEndings
)
self.bigDocLimit = self._parseLine(
cnfParse, cnfSec, "bigdoclimit", self.CNF_INT, self.bigDocLimit
)
## Backup
cnfSec = "Backup"
@@ -412,6 +416,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,"bigdoclimit", str(self.bigDocLimit))
## Backup
cnfSec = "Backup"
+3 -2
View File
@@ -85,7 +85,8 @@ class ToMarkdown(Tokenizer):
# indicating a new paragraph.
if tType == self.T_EMPTY:
if len(thisPar) > 0:
self.theResult += "%s\n\n" % " ".join(thisPar)
tTemp = "\n".join(thisPar)
self.theResult += "%s\n\n" % tTemp.rstrip()
thisPar = []
elif tType == self.T_HEAD1:
@@ -104,7 +105,7 @@ class ToMarkdown(Tokenizer):
self.theResult += "%s\n\n" % tText
elif tType == self.T_SKIP:
self.theResult += "\n\n\n\n"
self.theResult += "\n\n\n"
elif tType == self.T_TEXT:
thisPar.append(tText)
+2 -2
View File
@@ -122,8 +122,8 @@ class ToText(Tokenizer):
tText = self._centreText(tText,self.wordWrap)
self.theResult += "%s\n\n" % tText
elif tType == self.T_SEP:
self.theResult += "\n\n\n\n"
elif tType == self.T_SKIP:
self.theResult += "\n\n\n"
elif tType == self.T_TEXT:
thisPar.append(tText)
+21 -6
View File
@@ -44,7 +44,6 @@ class GuiConfigEditor(QDialog):
self.setWindowTitle("Preferences")
self.guiDeco = self.theParent.theTheme.loadDecoration("settings",(64,64))
self.theProject.countStatus()
self.tabMain = GuiConfigEditGeneral(self.theParent)
self.tabEditor = GuiConfigEditEditor(self.theParent)
@@ -174,11 +173,24 @@ class GuiConfigEditGeneral(QWidget):
self.spellToolList.setCurrentIndex(toolIdx)
self._doUpdateSpellTool(0)
self.spellLangForm.addWidget(QLabel("Provider"), 0, 0)
self.spellLangForm.addWidget(self.spellToolList, 0, 1)
self.spellLangForm.addWidget(QLabel("Language"), 1, 0)
self.spellLangForm.addWidget(self.spellLangList, 1, 1)
self.spellLangForm.setColumnStretch(2, 1)
self.spellBigDoc = QSpinBox(self)
self.spellBigDoc.setMinimum(10)
self.spellBigDoc.setMaximum(10000)
self.spellBigDoc.setSingleStep(10)
self.spellBigDoc.setToolTip((
"Disable spell checking when loading large documents. "
"Spell checking will only run on paragraphs you edit."
))
self.spellBigDoc.setValue(self.mainConf.bigDocLimit)
self.spellLangForm.addWidget(QLabel("Provider"), 0, 0)
self.spellLangForm.addWidget(self.spellToolList, 0, 1, 1, 3)
self.spellLangForm.addWidget(QLabel("Language"), 1, 0)
self.spellLangForm.addWidget(self.spellLangList, 1, 1, 1, 3)
self.spellLangForm.addWidget(QLabel("Size limit"), 2, 0)
self.spellLangForm.addWidget(self.spellBigDoc, 2, 1)
self.spellLangForm.addWidget(QLabel("kb"), 2, 2)
self.spellLangForm.setColumnStretch(4, 1)
# AutoSave
self.autoSave = QGroupBox("Automatic Save", self)
@@ -252,6 +264,7 @@ class GuiConfigEditGeneral(QWidget):
guiDark = self.guiDarkIcons.isChecked()
spellTool = self.spellToolList.currentData()
spellLanguage = self.spellLangList.currentData()
bigDocLimit = self.spellBigDoc.value()
autoSaveDoc = self.autoSaveDoc.value()
autoSaveProj = self.autoSaveProj.value()
backupPath = self.projBackupPath.text()
@@ -266,6 +279,7 @@ class GuiConfigEditGeneral(QWidget):
self.mainConf.guiDark = guiDark
self.mainConf.spellTool = spellTool
self.mainConf.spellLanguage = spellLanguage
self.mainConf.bigDocLimit = bigDocLimit
self.mainConf.autoSaveDoc = autoSaveDoc
self.mainConf.autoSaveProj = autoSaveProj
self.mainConf.backupPath = backupPath
@@ -541,6 +555,7 @@ class GuiConfigEditEditor(QWidget):
self.outerBox.addWidget(self.quoteStyle, 2, 1, 2, 1)
self.outerBox.addWidget(self.showGuides, 4, 1)
self.outerBox.setColumnStretch(2, 1)
self.outerBox.setRowStretch(5, 1)
self.setLayout(self.outerBox)
return
-1
View File
@@ -50,7 +50,6 @@ class GuiExport(QDialog):
self.guiDeco = self.theParent.theTheme.loadDecoration("export",(64,64))
self.theProject.countStatus()
self.tabMain = GuiExportMain(self.theParent, self.theProject, self.optState)
self.tabPandoc = GuiExportPandoc(self.theParent, self.theProject, self.optState)
+72 -10
View File
@@ -53,6 +53,7 @@ class GuiDocEditor(QTextEdit):
self.wordCount = 0
self.paraCount = 0
self.lastEdit = 0
self.bigDoc = False
self.nonWord = "\"'"
# Typography
@@ -114,17 +115,22 @@ class GuiDocEditor(QTextEdit):
return
def clearEditor(self):
"""Clear the current document and reset all document related
flags and counters.
"""
self.nwDocument.clearDocument()
self.setReadOnly(True)
self.clear()
self.wcTimer.stop()
self.theHandle = None
self.charCount = 0
self.wordCount = 0
self.paraCount = 0
self.lastEdit = 0
self.theHandle = None
self.charCount = 0
self.wordCount = 0
self.paraCount = 0
self.lastEdit = 0
self.bigDoc = False
self.hasSelection = False
self.setDocumentChanged(False)
@@ -214,7 +220,19 @@ class GuiDocEditor(QTextEdit):
return False
self.hLight.setHandle(tHandle)
# Check that the document is not too big for full, initial spell
# checking. If it is too big, we switch to only check as we type
self._checkDocSize(len(theDoc))
spTemp = self.hLight.spellCheck
if self.bigDoc:
self.hLight.spellCheck = False
bfTime = time()
self.setPlainText(theDoc)
afTime = time()
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
self.setCursorPosition(self.nwDocument.theItem.cursorPos)
self.lastEdit = time()
self._runCounter()
@@ -227,8 +245,18 @@ class GuiDocEditor(QTextEdit):
else:
self.theParent.noticeBar.showNote("This document is read only.")
self.hLight.spellCheck = spTemp
return True
def replaceText(self, theText):
"""Replaces the text of the current document with the provided
text. This also clears undo history.
"""
self.setPlainText(theText)
self.setDocumentChanged(True)
return
def saveText(self):
if self.nwDocument.theItem is None:
@@ -250,7 +278,7 @@ class GuiDocEditor(QTextEdit):
def updateDocMargins(self):
"""Automatically adjust the margins so the text is centred, but
only if Config.textFixedW is set to True.
only if Config.textFixedW is enabled or we're in Zen mode.
"""
if self.mainConf.textFixedW or self.theParent.isZenMode:
@@ -273,7 +301,15 @@ class GuiDocEditor(QTextEdit):
docFormat = self.qDocument.rootFrame().frameFormat()
docFormat.setLeftMargin(tM)
docFormat.setRightMargin(tM)
# Updating root frame triggers a QTextDocument->contentsChange
# signal, which we do not want as it re-runs the syntax
# highlighter and spell checker, so we block it briefly.
# We then emit a signal that does not trigger re-highlighting.
self.qDocument.blockSignals(True)
self.qDocument.rootFrame().setFrameFormat(docFormat)
self.qDocument.blockSignals(False)
self.qDocument.contentsChange.emit(0,0,0)
return
@@ -339,18 +375,26 @@ class GuiDocEditor(QTextEdit):
self.theParent.mainMenu.setSpellCheck(theMode)
self.theProject.setSpellCheck(theMode)
self.hLight.setSpellCheck(theMode)
self.hLight.rehighlight()
self.reHighlightDocument()
logger.verbose("Spell check is set to %s" % str(theMode))
return True
def updateSpellCheck(self):
def reHighlightDocument(self):
"""Rerun the highlighter to update spell checking status of the
currently loaded text.
currently loaded text. The fastest way to do this, at least as
of Qt 5.13, is to clear the text and put it back.
"""
if self.spellCheck:
self.hLight.rehighlight()
theText = self.getText()
self.clear()
bfTime = time()
self.setPlainText(theText)
afTime = time()
logger.debug("Document re-highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
return True
##
@@ -582,6 +626,9 @@ class GuiDocEditor(QTextEdit):
return
def _docChange(self, thePos, charsRemoved, charsAdded):
"""Triggered by QTextDocument->contentsChanged. This also
triggers the syntax highlighter.
"""
self.lastEdit = time()
if not self.docChanged:
self.setDocumentChanged(True)
@@ -673,9 +720,24 @@ class GuiDocEditor(QTextEdit):
self.theParent.statusBar.setCounts(self.charCount,self.wordCount,self.paraCount)
self.theParent.treeView.propagateCount(tHandle, self.wordCount)
self.theParent.treeView.projectWordCount()
self._checkDocSize(self.charCount)
return
def _checkDocSize(self, theSize):
"""Check if document size crosses the big document limit set in
config. If so, we will set the big document flag to True.
"""
if theSize > self.mainConf.bigDocLimit*1000:
logger.info(
"The document size is %d > %d, big doc mode is enabled" % (
theSize, self.mainConf.bigDocLimit*1000
))
self.bigDoc = True
else:
self.bigDoc = False
return
def _wrapSelection(self, tBefore, tAfter):
"""Wraps the selected text in whatever is in tBefore and tAfter.
If there is no selection, the autoSelect setting decides the
+1 -1
View File
@@ -608,7 +608,7 @@ class GuiMainMenu(QMenuBar):
self.aReRunSpell = QAction("Re-Run Spell Check", self)
self.aReRunSpell.setStatusTip("Run the spell checker on current document")
self.aReRunSpell.setShortcut("F7")
self.aReRunSpell.triggered.connect(self.theParent.docEditor.updateSpellCheck)
self.aReRunSpell.triggered.connect(self.theParent.docEditor.reHighlightDocument)
self.toolsMenu.addAction(self.aReRunSpell)
# Tools > Separator
+33 -53
View File
@@ -101,54 +101,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"value" : self._makeFormat(self.colVal),
}
# Headers
self.hRules = []
self.hRules.append((
r"^(#{1}) (.*)[^\n]", {
0 : self.hStyles["header1"],
1 : self.hStyles["header1h"],
}
))
self.hRules.append((
r"^(#{2}) (.*)[^\n]", {
0 : self.hStyles["header2"],
1 : self.hStyles["header2h"],
}
))
self.hRules.append((
r"^(#{3}) (.*)[^\n]", {
0 : self.hStyles["header3"],
1 : self.hStyles["header3h"],
}
))
self.hRules.append((
r"^(#{4}) (.*)[^\n]", {
0 : self.hStyles["header4"],
1 : self.hStyles["header4h"],
}
))
# Keyword/Value
# self.hRules.append((
# r"^(@.+?)\s*:\s*(.+?)$", {
# 1 : self.hStyles["keyword"],
# 2 : self.hStyles["value"],
# }
# ))
# Comments
self.hRules.append((
r"^%.*$", {
0 : self.hStyles["hidden"],
}
))
self.hRules.append((
r"^(%)(synopsis:\s+)(.*)$", {
1 : self.hStyles["hidden"],
2 : self.hStyles["keyword"],
3 : self.hStyles["hidden"],
}
))
# Trailing Spaces, 2+
self.hRules.append((
@@ -248,12 +201,16 @@ 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
text paragraphs.
"""
if self.theHandle is None:
if self.theHandle is None or not theText:
return
if theText.startswith("@"):
# Highlighting of keywords and commands
if theText.startswith("@"): # Keywords and commands
tItem = self.theParent.theProject.getItem(self.theHandle)
isValid, theBits, thePos = self.theIndex.scanThis(theText)
isGood = self.theIndex.checkThese(theBits, tItem)
@@ -272,11 +229,30 @@ class GuiDocHighlighter(QSyntaxHighlighter):
kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
self.setFormat(xPos, xLen, kwFmt)
# We're done, no need to continue
# We never want to run the spell checker on keyword/values,
# so we force a return here
return
else:
# For other text, just use our regex rules
elif theText.startswith("# "): # Header 1
self.setFormat(0, 1, self.hStyles["header1h"])
self.setFormat(1, len(theText), self.hStyles["header1"])
elif theText.startswith("## "): # Header 2
self.setFormat(0, 2, self.hStyles["header2h"])
self.setFormat(2, len(theText), self.hStyles["header2"])
elif theText.startswith("### "): # Header 3
self.setFormat(0, 3, self.hStyles["header3h"])
self.setFormat(3, len(theText), self.hStyles["header3"])
elif theText.startswith("#### "): # Header 4
self.setFormat(0, 4, self.hStyles["header4h"])
self.setFormat(4, len(theText), self.hStyles["header4"])
elif theText.startswith("%"): # Comments
self.setFormat(0, len(theText), self.hStyles["hidden"])
else: # Text Paragraph
for rX, xFmt in self.rxRules:
rxItt = rX.globalMatch(theText, 0)
while rxItt.hasNext():
@@ -310,6 +286,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
##
def _makeFormat(self, fmtCol=None, fmtStyle=None, fmtSize=None):
"""Generate a valid character format to be applied to the text
that is to be highlighted.
"""
theFormat = QTextCharFormat()
if fmtCol is not None:
+8 -3
View File
@@ -107,6 +107,7 @@ class GuiMain(QMainWindow):
self.viewPane.setLayout(self.docView)
self.splitView = QSplitter(Qt.Horizontal)
self.splitView.setOpaqueResize(False)
self.splitView.addWidget(self.editPane)
self.splitView.addWidget(self.viewPane)
@@ -119,6 +120,7 @@ class GuiMain(QMainWindow):
self.splitMain = QSplitter(Qt.Horizontal)
self.splitMain.setContentsMargins(4,4,4,4)
self.splitMain.setOpaqueResize(False)
self.splitMain.addWidget(self.treePane)
self.splitMain.addWidget(self.tabWidget)
self.splitMain.setSizes(self.mainConf.mainPanePos)
@@ -426,17 +428,20 @@ class GuiMain(QMainWindow):
)
if inPath:
loadFile = inPath[0]
self.mainConf.setLastPath(loadFile)
else:
return False
if loadFile.strip() == "":
return False
theText = None
try:
with open(loadFile,mode="rt",encoding="utf8") as inFile:
theText = inFile.read()
self.mainConf.setLastPath(loadFile)
except Exception as e:
self.makeAlert(
["Could not read file. The file cannot be a binary file.",str(e)],
["Could not read file. The file must be an existing text file.",str(e)],
nwAlert.ERROR
)
return False
@@ -460,7 +465,7 @@ class GuiMain(QMainWindow):
else:
return False
self.docEditor.setText(theText)
self.docEditor.replaceText(theText)
return True
+3
View File
@@ -582,6 +582,9 @@ class NWProject():
return True
def countStatus(self):
"""Count how many times the various status flags are used in the
project tree.
"""
self.statusItems.resetCounts()
self.importItems.resetCounts()
for nwItem in self.projTree.values():
+2
View File
@@ -41,6 +41,8 @@ class NWStatus():
return True
def lookupEntry(self, theLabel):
if theLabel is None:
return None
theLabel = theLabel.strip()
if theLabel in self.theMap.keys():
return self.theMap[theLabel]
+1 -1
View File
@@ -6,7 +6,7 @@ with open("README.md", "r") as inFile:
setuptools.setup(
name = "novelWriter",
version = "0.4.1",
version = "0.4.2",
author = "Veronica Berglyd Olsen",
author_email = "code@vkbo.net",
description = "A markdown-like document editor for writing novels",
+2 -1
View File
@@ -1,5 +1,5 @@
[Main]
timestamp = 2019-11-09 14:38:32
timestamp = 2019-11-19 21:49:29
theme = default
syntax = default_light
guidark = False
@@ -36,6 +36,7 @@ spelltool = internal
spellcheck = en
showtabsnspaces = False
showlineendings = False
bigdoclimit = 800
[Backup]
backuppath =