diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py
index 16a8285f..baa5165d 100644
--- a/novelwriter/core/tohtml.py
+++ b/novelwriter/core/tohtml.py
@@ -154,25 +154,31 @@ class ToHtml(Tokenizer):
parStyle = None
tmpResult = []
- for tType, tLine, tDirty, tFormat, tStyle in self._theTokens:
+ for tType, tLine, tText, tFormat, tStyle in self._theTokens:
- # Replace < and > and recompute formatting positions
- cText = []
- i = 0
- for c in tDirty:
- if c == "<":
- cText.append("<")
- tFormat = [[a + 3 if a > i else a, b, c] for a, b, c in tFormat]
- i += 4
- elif c == ">":
- cText.append(">")
- tFormat = [[a + 3 if a > i else a, b, c] for a, b, c in tFormat]
- i += 4
- else:
- cText.append(c)
- i += 1
+ # Replace < and > with HTML entities
+ if tFormat:
+ # If we have formatting, we must recompute the locations
+ cText = []
+ i = 0
+ for c in tText:
+ if c == "<":
+ cText.append("<")
+ tFormat = [[a + 3 if a > i else a, b, c] for a, b, c in tFormat]
+ i += 4
+ elif c == ">":
+ cText.append(">")
+ tFormat = [[a + 3 if a > i else a, b, c] for a, b, c in tFormat]
+ i += 4
+ else:
+ cText.append(c)
+ i += 1
- tText = "".join(cText)
+ tText = "".join(cText)
+
+ else:
+ # If we don't have formatting, we can do a plain replace
+ tText = tText.replace("<", "<").replace(">", ">")
# Styles
aStyle = []
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index 902e1db2..72c142a0 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -184,6 +184,7 @@ class GuiDocViewer(QTextBrowser):
logger.error("Failed to generate preview for document with handle '%s'", tHandle)
logException()
self.setText(self.tr("An error occurred while generating the preview."))
+ qApp.restoreOverrideCursor()
return False
# Refresh the tab stops
diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py
index d8e3d2da..11d89572 100644
--- a/tests/test_core/test_core_tohtml.py
+++ b/tests/test_core/test_core_tohtml.py
@@ -417,6 +417,27 @@ def testCoreToHtml_SpecialCases(mockGUI):
"
Test > text <bold> and more.
\n"
)
+ # Test for bug #950
+ # =================
+ # See: https://github.com/vkbo/novelWriter/issues/950
+
+ theHtml.setComments(True)
+ theHtml._theText = "% Test > text _<**bold**>_ and more.\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "\n"
+ )
+
+ theHtml._theText = "## Heading <1>\n"
+ theHtml.tokenizeText()
+ theHtml.doConvert()
+ assert theHtml.theResult == (
+ "Heading <1>
\n"
+ )
+
# END Test testCoreToHtml_SpecialCases
diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py
index cfe3f59e..e971c743 100644
--- a/tests/test_gui/test_gui_docviewer.py
+++ b/tests/test_gui/test_gui_docviewer.py
@@ -24,8 +24,10 @@ import pytest
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import qApp, QAction, QMessageBox
+from mock import causeException
from novelwriter.enum import nwDocAction
+from novelwriter.core import ToHtml
keyDelay = 2
typeDelay = 1
@@ -52,7 +54,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
assert nwGUI.theIndex._refIndex != {}
# Select a document in the project tree
- assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
+ nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
# Middle-click the selected item
theItem = nwGUI.treeView._getTreeItem("88243afbe5ed8")
@@ -67,14 +69,14 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
assert nwGUI.docViewer.toPlainText() == origText
# Cursor line
- assert not nwGUI.docViewer.setCursorLine("not a number")
- assert nwGUI.docViewer.setCursorLine(3)
+ assert nwGUI.docViewer.setCursorLine("not a number") is False
+ assert nwGUI.docViewer.setCursorLine(3) is True
theCursor = nwGUI.docViewer.textCursor()
assert theCursor.position() == 40
# Cursor position
- assert not nwGUI.docViewer.setCursorPosition("not a number")
- assert nwGUI.docViewer.setCursorPosition(100)
+ assert nwGUI.docViewer.setCursorPosition("not a number") is False
+ assert nwGUI.docViewer.setCursorPosition(100) is True
# Select word
nwGUI.docViewer._makeSelection(QTextCursor.WordUnderCursor)
@@ -83,17 +85,17 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
qClip.clear()
# Cut
- assert nwGUI.docViewer.docAction(nwDocAction.CUT)
+ assert nwGUI.docViewer.docAction(nwDocAction.CUT) is True
assert qClip.text() == "laoreet"
qClip.clear()
# Copy
- assert nwGUI.docViewer.docAction(nwDocAction.COPY)
+ assert nwGUI.docViewer.docAction(nwDocAction.COPY) is True
assert qClip.text() == "laoreet"
qClip.clear()
# Select Paragraph
- assert nwGUI.docViewer.docAction(nwDocAction.SEL_PARA)
+ assert nwGUI.docViewer.docAction(nwDocAction.SEL_PARA) is True
theCursor = nwGUI.docViewer.textCursor()
assert theCursor.selectedText() == (
"Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, "
@@ -103,26 +105,26 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
)
# Select All
- assert nwGUI.docViewer.docAction(nwDocAction.SEL_ALL)
+ assert nwGUI.docViewer.docAction(nwDocAction.SEL_ALL) is True
theCursor = nwGUI.docViewer.textCursor()
assert len(theCursor.selectedText()) == 3061
# Other actions
- assert not nwGUI.docViewer.docAction(nwDocAction.NO_ACTION)
+ assert nwGUI.docViewer.docAction(nwDocAction.NO_ACTION) is False
# Close document
nwGUI.docViewer.docHeader._closeDocument()
assert nwGUI.docViewer.docHandle() is None
# Action on no document
- assert not nwGUI.docViewer.docAction(nwDocAction.COPY)
+ assert nwGUI.docViewer.docAction(nwDocAction.COPY) is False
# Open again via menu
assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
nwGUI.mainMenu.aViewDoc.activate(QAction.Trigger)
# Select "Bod" link
- assert nwGUI.docViewer.setCursorPosition(27)
+ assert nwGUI.docViewer.setCursorPosition(27) is True
nwGUI.docViewer._makeSelection(QTextCursor.WordUnderCursor)
theRect = nwGUI.docViewer.cursorRect()
# qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.LeftButton, pos=theRect.center(), delay=100)
@@ -168,17 +170,23 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
assert nwGUI.docViewer.stickyRef is viewState
# Document footer show/hide synopsis
- assert nwGUI.viewDocument("f96ec11c6a3da")
+ assert nwGUI.viewDocument("f96ec11c6a3da") is True
assert len(nwGUI.docViewer.toPlainText()) == 4315
nwGUI.docViewer.docFooter._doToggleSynopsis(False)
assert len(nwGUI.docViewer.toPlainText()) == 4099
# Document footer show/hide comments
- assert nwGUI.viewDocument("846352075de7d")
+ assert nwGUI.viewDocument("846352075de7d") is True
assert len(nwGUI.docViewer.toPlainText()) == 675
nwGUI.docViewer.docFooter._doToggleComments(False)
assert len(nwGUI.docViewer.toPlainText()) == 635
+ # Crash the HTML rendering
+ with monkeypatch.context() as mp:
+ mp.setattr(ToHtml, "doConvert", causeException)
+ assert nwGUI.docViewer.loadText("846352075de7d") is False
+ assert nwGUI.docViewer.toPlainText() == "An error occurred while generating the preview."
+
# qtbot.stopForInteraction()
# END Test testGuiViewer_Main