diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91d91a86..9abf2ff6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: - name: Python Setup uses: actions/setup-python@v4 with: - python-version: 3 + python-version: "3.11" architecture: x64 - name: Install Packages (apt) @@ -48,7 +48,7 @@ jobs: - name: Python Setup uses: actions/setup-python@v4 with: - python-version: 3 + python-version: "3.11" architecture: x64 - name: Install Packages (pip) diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 448aca8b..00000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details -version: 2 -sphinx: - configuration: docs/source/conf.py -formats: - - htmlzip - - pdf -python: - install: - - requirements: docs/source/requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bbbfd74..4ac00b49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,62 @@ # novelWriter Changelog +## Version 2.1.1 [2023-11-05] + +### Release Notes + +This is a patch release that fixes a layout issue and internationalisation issues with the new +Manuscript Build tool. It also fixes a number of issues related to bugs in the underlying Qt +framework that affects drag and drop functionality in the project tree. These issues were mostly +only affecting Debian Linux package releases. + +Other, minor issues related to updating the editor on colour theme change and project word list +changes have been fixed as well. See the full changelog for more details. + +### Detailed Changelog + +**Bugfixes** + +* Fix an issue with width of the last two columns on Selection page of the Build Settings dialog on + Windows. They were far too wide by default. Issue #1551. PR #1553. +* Fix an issue where a lot of string were not translated to the UI language in the new Manuscript + Build tool. Issue #1563. PR #1565. +* Fix an issue in the Document Viewer where it wouldn't scroll to a heading further down the page + when following a reference pointing to it. Issue #1566. PR #1568. +* Add back in checks for illegal drag and drop moves in the project tree. In 2.0, the logic here + was changed to set certain restrictions on the elements of the project tree itself, but there are + numerous bugs in the Qt framework related to drag and drop, so the checks are ignored on at least + Qt 5.15.8. In particular, it is possible to drop items on the root level, and it's possible to + move root items to other locations. Neither should be possible and will severely mess up the + project if done. Issue #1569. PR #1570. +* Add a custom autoscroll feature when dragging an item in the project tree to near the top or + bottom. This is actually a default feature of the tree widget in the Qt library, but this too is + broken in some versions of Qt 5.15.x. The default feature has been permanently disabled and + replaced by a custom written feature that behaves similarly. Issue #1561. PR #1571. +* Fix an issue where the editor document wasn't re-highlighted when the Syntax Theme for it was + changed. Issue #1535. PR #1573. +* Fix an issue where editing the Project Word List would not refresh the spell checking of the + editor. Issue #1559. PR #1573. + +**Usability** + +* Changed how the default UI language is selected. It used to default to the system locale, but + that is now changed to British English if the system local is not available in novelWriter. The + only real effects of this is that the dropdown box in Preferences now selects British English if + the system locale is not available rather than the first in the list (currently Deutch). The + second effect is that the language on buttons and other Qt components will match the rest of the + UI. Issue #1564. PR #1565. +* There is a bug in Qt on Wayland desktops where menus don't open in the correct location. + According to one Qt ticket, QTBUG-68636, this can be mitigated by ensuring all QMenu instances + have a parent set. This does not fix all issues, but it should help. The menus without a parent + set have now been updated. Issue #1536. PR #1572. + +**Documentation** + +* Fixed a number of spelling errors and typing mistakes in the documentation for 2.1. Contributed + by @nisemono-neko. PR #1567. + +---- + ## Version 2.1 [2023-10-17] ### Release Notes diff --git a/docs/source/conf.py b/docs/source/conf.py index 176c13b8..72ddad50 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,7 +27,7 @@ with open(initFile) as inFile: else: release = "unknown" -version = release.split("-")[0] +version = release.partition("-")[0] # -- General Configuration --------------------------------------------------- @@ -67,11 +67,6 @@ html_sidebars = { "**": ["navbar-logo", "sidebar-title", "sbt-sidebar-nav"], } -# -- Options for HTMLHelp Output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = "novelWriterDoc" - # -- Options for LaTeX Output ------------------------------------------------ latex_elements = { @@ -84,11 +79,6 @@ latex_elements = { "figure_align": "htbp", } latex_logo = "_static/novelwriter-pdf.png" - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). latex_documents = [( - master_doc, "manual.tex", "User Guide", - author, "manual" + master_doc, "manual.tex", "User Guide", author, "manual" )] diff --git a/docs/source/int_customise.rst b/docs/source/int_customise.rst index 1001c989..15a7a348 100644 --- a/docs/source/int_customise.rst +++ b/docs/source/int_customise.rst @@ -87,7 +87,7 @@ set as ``name`` inside the file. Gustom GUI and Icons Theme -------------------------- -A GUI theme ``.conf`` file consists of the follwing settings: +A GUI theme ``.conf`` file consists of the following settings: .. code-block:: cfg @@ -127,7 +127,7 @@ In the Main section you must at least define the ``name`` and ``icontheme`` sett ``typicons_light`` or ``typicons_dark``, or to an icon theme in your custom icons directory. The setting must match the icon theme's folder name. -The Palette values correspond the the Qt enum values for QPalette::ColorRole, see the +The Palette values correspond to the Qt enum values for QPalette::ColorRole, see the `Qt documentation `_ for more details. The colour values are RGB numbers on the format ``r, g, b`` where each is an integer from to 255. Omitted values are not loaded and will use default values. @@ -168,7 +168,7 @@ A syntax theme ``.conf`` file consists of the follwing settings: modifier = 0, 0, 0 In the Main section, you must define at least the ``name`` setting. The Syntax colour values are -RGB numbers on the format ``r, g, b`` where each is an integer from to 255. Omitted values default +RGB numbers of the format ``r, g, b`` where each is an integer from to 255. Omitted values default to black, except ``background`` which defaults to white, .. versionadded:: 2.2 diff --git a/docs/source/int_glossary.rst b/docs/source/int_glossary.rst index a57ac414..2cead9cf 100644 --- a/docs/source/int_glossary.rst +++ b/docs/source/int_glossary.rst @@ -32,7 +32,7 @@ Glossary for more details. Reference - A references is one of a set of :term:`keywords` that can be used to link to a + A reference is one of a set of :term:`keywords` that can be used to link to a :term:`tag` in another document. The reference keywords are specific to the different :term:`root folder` types. A full overview is available in the :ref:`a_references` chapter. @@ -55,7 +55,7 @@ Glossary Keyword A keyword in novelWriter is a special command you put in the text of your documents. They are - not standard Markdown, but is used in novelWriter to add information that is interpreted by + not standard Markdown, but are used in novelWriter to add information that is interpreted by the application. For instance, keywords are used for :term:`tags` and :term:`references`. diff --git a/docs/source/int_introduction.rst b/docs/source/int_introduction.rst index d4c99188..ca344eb8 100644 --- a/docs/source/int_introduction.rst +++ b/docs/source/int_introduction.rst @@ -10,7 +10,7 @@ Key Features At its core, novelWriter is a multi-document plain text editor. It uses a markup syntax inspired by Markdown_ to apply simple formatting to the text. It is designed for writing fiction, so the formatting features available are limited to those relevant for this purpose. It is *not* suitable -for technical writing, and it is *not* a full-feature Markdown editor. +for technical writing, and it is *not* a full-featured Markdown editor. Your novel project is organised as a collection of separate plain text documents instead of a single, large document. The idea here is to make it easier to reorganise your project structure @@ -32,11 +32,11 @@ Below are some key features of novelWriter. formatting text. Formatting is therefore limited to a small set of formatting tags for simple things like text emphasis and paragraph alignment. When you really want to focus on just writing, you can switch the editor into :guilabel:`Focus Mode` where only the text editor panel - itself is vissible, and the project structure view is hidden away. + itself is visible, and the project structure view is hidden away. **Keep an eye on your notes** The main window can optionally show a document viewer to the right of the editor. This view - panel is intended for displaying another scene document, you character notes, plot notes, or any + panel is intended for displaying another scene document, your character notes, plot notes, or any other document you may need to reference while writing. It is not intended as a preview panel for the document you're editing, but if you wish, you can also use it for this purpose. diff --git a/docs/source/int_overview.rst b/docs/source/int_overview.rst index 09d8358f..6cba93ff 100644 --- a/docs/source/int_overview.rst +++ b/docs/source/int_overview.rst @@ -14,8 +14,8 @@ novelWriter is built as a cross-platform application using `Python 3 `_ for the user interface. novelWriter is built for Linux first, and this is where it works best. However, it also runs fine -on Windows and MacOS due to the cross-platform framework it's build on. The author of the -application doesn't own a Mac, so on-going Mac support is dependant on user feedback and user +on Windows and MacOS due to the cross-platform framework it's built on. The author of the +application doesn't own a Mac, so on-going Mac support is dependent on user feedback and user contributions. Spell checking in novelWriter is provided by a third party library called @@ -36,7 +36,7 @@ read on. This chapter explains the basics of how the application works and what it can and cannot do. :ref:`a_ui_project` – Recommended Reading - This chapter will give you a more detailed explanation of how you the user interface components + This chapter will give you a more detailed explanation of how you can use the user interface components to organise and view your project work. :ref:`a_ui_writing` – Recommended Reading @@ -68,7 +68,7 @@ read on. Organising Your Projects ======================== -In addition to manage a collection of plain text files, novelWriter can interpret and map the +In addition to managing a collection of plain text files, novelWriter can interpret and map the structure of your novel and show you additional information about its flow and content. In order to take advantage of these features, you must structure your text in a specific way and add some meta data for it to extract. diff --git a/docs/source/usage_breakdown.rst b/docs/source/usage_breakdown.rst index b6899f44..b0b0c8f7 100644 --- a/docs/source/usage_breakdown.rst +++ b/docs/source/usage_breakdown.rst @@ -105,7 +105,7 @@ much all collected meta data is available here in different columns. You can select which novel root folder to display from the dropdown box, and you can select which columns to show or hide from the menu button. You can also rearrange the columns by drag and drop. -The app will remember you column order and size between sessions, and for each individual project. +The app will remember your column order and size between sessions, and for each individual project. Colour Themes @@ -220,7 +220,7 @@ makes it easier to track them with version control software. Drive is mapped on Linux Gnome desktops using gvfs/gio. .. caution:: - You should not add additional files to the project folder yourself. Nor should you,a s a rule, + You should not add additional files to the project folder yourself. Nor should you, as a rule, manually edit files within it. If you really must manually edit the text files, e.g. with some automated task you want to perform, you need to rebuild the :term:`Project Index` when you open the project again. diff --git a/docs/source/usage_format.rst b/docs/source/usage_format.rst index 4b159491..567695ff 100644 --- a/docs/source/usage_format.rst +++ b/docs/source/usage_format.rst @@ -34,7 +34,7 @@ have a distinct colour, and the references themselves will get a colour if they references will get a squiggly error line underneath. The same applies to duplicate tags. There are a number of syntax highlighter colour themes available, both for light and dark GUIs. You -can select them for :guilabel:`Preferences`. +can select them from :guilabel:`Preferences`. .. _a_fmt_head: @@ -72,7 +72,7 @@ For headers level one and two, adding a ``!`` modifies the behaviour of the head ``#! Title Text`` This tells the build tool that the level one heading is intended to be used for the novel's main title, like for instance on the front page. When building the manuscript, this will use a - different styling and will exclude the title from for instance a Table of Contents in Libre + different styling and will exclude the title from, for instance, a Table of Contents in Libre Office. ``##! Title Text`` @@ -170,7 +170,7 @@ in a special manner and will show up in the :ref:`a_ui_outline` in a dedicated c indicate this by altering the colour of the word. ``% text...`` - This is a comment. The text is not renderred by default (this can be overridden), seen in the + This is a comment. The text is not rendered by default (this can be overridden), seen in the document viewer, or counted towards word counts. ``% Synopsis: text...`` @@ -196,10 +196,10 @@ Tags use the command ``@tag:`` to define a tag. The tag can be set once per sect heading. Setting it multiple times under the same heading will just override the previous setting. ``@tag: value`` - A tag commad followed by the tag value, like for instance the name of a character. + A tag command followed by the tag value, like for instance the name of a character. References can be set anywhere within a section, and are collected according to their category. -References are on the form: +References are in the form: ``@keyword: value`` A reference keyword followed by a value, or a comma separated list of values. @@ -236,7 +236,7 @@ Examples: "``>> Centred text <<``", "The text paragraph is centred." "``> Left indented text``", "The text has an increased left margin." "``Right indented text <``", "The text has an increased right margin." - "``> Left/right indented text <``", "The text has an both margins increased." + "``> Left/right indented text <``", "The text has both margins increased." .. note:: The text editor will not show the alignment and indentation live. But the viewer will show them diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst index 764b5240..86e60afa 100644 --- a/docs/source/usage_shortcuts.rst +++ b/docs/source/usage_shortcuts.rst @@ -4,7 +4,7 @@ Keyboard Shortcuts ****************** -Most features in novelWriter are available as keyboard shortcuts. This is a reference list if these +Most features in novelWriter are available as keyboard shortcuts. This is a reference list of those shortcuts. Most of them are also listed in the application's user interface. .. note:: diff --git a/i18n/README.md b/i18n/README.md index 159eee73..1c5303a1 100644 --- a/i18n/README.md +++ b/i18n/README.md @@ -48,6 +48,7 @@ instance `en_GB` for British English. You will need the translation tool Qt 5 Linguist on your system. For Ubuntu/Debian, run: + ```bash sudo apt install qttools5-dev-tools ``` @@ -75,6 +76,7 @@ The `i18n` folder at the root of the repository contains the `nw_XX.ts` translat Whether to add a new language to the translation framework, or to update the file against the current source code, you must first run the `qtlupdate` command: + ```bash python3 pkgutils.py qtlupdate i18n/nw_XX.ts ``` @@ -86,6 +88,10 @@ be a valid ISO language code, otherwise novelWriter will not accept the file. For instance, the French translation uses the language code `fr_FR`, so its translation file will be `nw_fr_FR.ts` +Note: The `qtlupdate` command needs the `lupdate` tool provided by PyQt6, which uses the latest +TS file format. The tool in PyQt5 generates an older file format. On Debian/Ubuntu it is provided +by the package `pyqt6-dev-tools`. + ### Edit the Translation File in Qt Linguist @@ -100,6 +106,7 @@ Please select "English" and "United Kingdom" as the _source_ language if prompte The application does not use `.ts` files directly. The `novelwriter/assets/i18n/nw_XX.qm` files are the actual files used to translate the GUI into another language other than the default British English. These files are not generated by default, but they can be built with: + ```bash python3 pkgutils.py qtlrelease ``` diff --git a/novelwriter/assets/text/release_notes.htm b/novelwriter/assets/text/release_notes.htm index 521a5f6d..c74a1574 100644 --- a/novelwriter/assets/text/release_notes.htm +++ b/novelwriter/assets/text/release_notes.htm @@ -5,6 +5,8 @@

Release Notes for 2.1

Released on 17 October 2023

+

Scroll down for Patch Notes

+

The primary focus of this release has been a complete redesign of the Build Tool, that is, the tool that assembles your project into a manuscript document. The new tool, called the "Manuscript Build Tool" allows you to define multiple build definitions for your project. The build definitions @@ -30,5 +32,16 @@ a full list of changes, see the detailed changelogs.

See also the Releases page.

+

Patch Notes

+ +

Patch 2.2.1 – 5 November 2023

+ +

This is a patch release that fixes a layout issue and internationalisation issues with the new +Manuscript Build tool. It also fixes a number of issues related to bugs in the underlying Qt +framework that affects drag and drop functionality in the project tree. These issues were mostly +only affecting Debian Linux package releases.

+

Other, minor issues related to updating the editor on colour theme change and project word list +changes have been fixed as well. See the full changelog for more details.

+ diff --git a/novelwriter/config.py b/novelwriter/config.py index c7994d1c..50c8c1ab 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -81,10 +81,12 @@ class Config: # Localisation # Note that these paths must be strings - self._qLocale = QLocale.system() - self._qtTrans = {} + self._nwLangPath = self._appPath / "assets" / "i18n" self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath) - self._nwLangPath = str(self._appPath / "assets" / "i18n") + + wantedLocale = self._nwLangPath / f"nw_{QLocale.system().name()}.qm" + self._qLocale = QLocale.system() if wantedLocale.exists() else QLocale("en_GB") + self._qtTrans = {} # PDF Manual pdfDocs = self._appPath / "assets" / "manual.pdf" @@ -425,7 +427,7 @@ class Config: else: return [] - for qmFile in Path(self._nwLangPath).iterdir(): + for qmFile in self._nwLangPath.iterdir(): qmName = qmFile.name if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)): continue @@ -493,8 +495,8 @@ class Config: self._qtTrans = {} langList = [ - (self._qtLangPath, "qtbase"), # Qt 5.x - (self._nwLangPath, "nw"), # novelWriter + (self._qtLangPath, "qtbase"), # Qt 5.x + (str(self._nwLangPath), "nw"), # novelWriter ] for lngPath, lngBase in langList: for lngCode in self._qLocale.uiLanguages(): diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index f506cef5..931525ea 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -32,7 +32,7 @@ from enum import Enum from typing import Iterable from pathlib import Path -from PyQt5.QtCore import QT_TRANSLATE_NOOP +from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication from novelwriter import CONFIG from novelwriter.enum import nwBuildFmt @@ -209,7 +209,7 @@ class BuildSettings: @staticmethod def getLabel(key: str) -> str: """Extract the GUI label for a specific setting.""" - return SETTINGS_LABELS.get(key, "ERROR") + return QCoreApplication.translate("Builds", SETTINGS_LABELS.get(key, "ERROR")) def getStr(self, key: str) -> str: """Type safe value access for strings.""" diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 068bb036..05273bae 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -404,7 +404,7 @@ class NWIndex: tagName = tBits[1] self._tagsIndex.add(tagName, tHandle, sTitle, itemClass) self._itemIndex.setHeadingTag(tHandle, sTitle, tagName) - tags[tagName] = True + tags[tagName.lower()] = True else: self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0]) diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py index 412dcedf..66515b26 100644 --- a/novelwriter/core/tohtml.py +++ b/novelwriter/core/tohtml.py @@ -171,7 +171,7 @@ class ToHtml(Tokenizer): pStyle = None lines = [] - for tType, tLine, tText, tFormat, tStyle in self._tokens: + for tType, nHead, tText, tFormat, tStyle in self._tokens: # Replace < and > with HTML entities if tFormat: @@ -230,7 +230,7 @@ class ToHtml(Tokenizer): hStyle = "" if self._linkHeaders: - aNm = f"" + aNm = f"" else: aNm = "" diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index 197d8603..c1a51181 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -404,23 +404,22 @@ class Tokenizer(ABC): The format of the token list is an entry with a five-tuple for each line in the file. The tuple is as follows: 1: The type of the block, self.T_* - 2: The line in the file where this block occurred + 2: The header number under which the text is placed 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_* """ self._tokens = [] tmpMarkdown = [] - nLine = 0 + nHead = 0 breakNext = False for aLine in self._text.splitlines(): - nLine += 1 sLine = aLine.strip().lower() # Check for blank lines if len(sLine) == 0: self._tokens.append(( - self.T_EMPTY, nLine, "", None, self.A_NONE + self.T_EMPTY, nHead, "", None, self.A_NONE )) if self._keepMarkdown: tmpMarkdown.append("\n") @@ -448,7 +447,7 @@ class Tokenizer(ABC): elif sLine == "[vspace]": self._tokens.append( - (self.T_SKIP, nLine, "", None, sAlign) + (self.T_SKIP, nHead, "", None, sAlign) ) continue @@ -456,11 +455,11 @@ class Tokenizer(ABC): nSkip = checkInt(sLine[8:-1], 0) if nSkip >= 1: self._tokens.append( - (self.T_SKIP, nLine, "", None, sAlign) + (self.T_SKIP, nHead, "", None, sAlign) ) if nSkip > 1: self._tokens += (nSkip - 1) * [ - (self.T_SKIP, nLine, "", None, self.A_NONE) + (self.T_SKIP, nHead, "", None, self.A_NONE) ] continue @@ -469,20 +468,20 @@ class Tokenizer(ABC): synTag = cLine[:9].lower() if synTag == "synopsis:": self._tokens.append(( - self.T_SYNOPSIS, nLine, cLine[9:].strip(), None, sAlign + self.T_SYNOPSIS, nHead, cLine[9:].strip(), None, sAlign )) if self._doSynopsis and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) else: self._tokens.append(( - self.T_COMMENT, nLine, aLine[1:].strip(), None, sAlign + self.T_COMMENT, nHead, aLine[1:].strip(), None, sAlign )) if self._doComments and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[0] == "@": self._tokens.append(( - self.T_KEYWORD, nLine, aLine[1:].strip(), None, sAlign + self.T_KEYWORD, nHead, aLine[1:].strip(), None, sAlign )) if self._doKeywords and self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -492,8 +491,9 @@ class Tokenizer(ABC): sAlign |= self.A_CENTRE sAlign |= self.A_PBB + nHead += 1 self._tokens.append(( - self.T_HEAD1, nLine, aLine[2:].strip(), None, sAlign + self.T_HEAD1, nHead, aLine[2:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -502,39 +502,44 @@ class Tokenizer(ABC): if self._isNovel: sAlign |= self.A_PBB + nHead += 1 self._tokens.append(( - self.T_HEAD2, nLine, aLine[3:].strip(), None, sAlign + self.T_HEAD2, nHead, aLine[3:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:4] == "### ": + nHead += 1 self._tokens.append(( - self.T_HEAD3, nLine, aLine[4:].strip(), None, sAlign + self.T_HEAD3, nHead, aLine[4:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:5] == "#### ": + nHead += 1 self._tokens.append(( - self.T_HEAD4, nLine, aLine[5:].strip(), None, sAlign + self.T_HEAD4, nHead, aLine[5:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:3] == "#! ": + nHead += 1 if self._isNovel: tStyle = self.T_TITLE else: tStyle = self.T_HEAD1 self._tokens.append(( - tStyle, nLine, aLine[3:].strip(), None, sAlign | self.A_CENTRE + tStyle, nHead, aLine[3:].strip(), None, sAlign | self.A_CENTRE )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:4] == "##! ": + nHead += 1 if self._isNovel: tStyle = self.T_UNNUM sAlign |= self.A_PBB @@ -542,7 +547,7 @@ class Tokenizer(ABC): tStyle = self.T_HEAD2 self._tokens.append(( - tStyle, nLine, aLine[4:].strip(), None, sAlign + tStyle, nHead, aLine[4:].strip(), None, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -586,7 +591,7 @@ class Tokenizer(ABC): # Process formats tLine, fmtPos = self._extractFormats(aLine) self._tokens.append(( - self.T_TEXT, nLine, tLine, fmtPos, sAlign + self.T_TEXT, nHead, tLine, fmtPos, sAlign )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -605,7 +610,7 @@ class Tokenizer(ABC): # Always add an empty line at the end of the file self._tokens.append(( - self.T_EMPTY, nLine, "", None, self.A_NONE + self.T_EMPTY, nHead, "", None, self.A_NONE )) if self._keepMarkdown: tmpMarkdown.append("\n") diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 76178d05..54cad2fc 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -176,6 +176,8 @@ class GuiPreferencesGeneral(QWidget): for lang, langName in theLangs: self.guiLocale.addItem(langName, lang) langIdx = self.guiLocale.findData(CONFIG.guiLocale) + if langIdx < 0: + langIdx = self.guiLocale.findData("en_GB") if langIdx != -1: self.guiLocale.setCurrentIndex(langIdx) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 525fd179..1555e61e 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -338,7 +338,7 @@ class GuiDocEditor(QPlainTextEdit): if self._docHandle is None: self.clearEditor() else: - self.redrawText() + self._qDocument.syntaxHighlighter.rehighlight() return @@ -980,7 +980,7 @@ class GuiDocEditor(QPlainTextEdit): uCursor = self.textCursor() pCursor = self.cursorForPosition(pos) - ctxMenu = QMenu() + ctxMenu = QMenu(self) # Follow if self._followTag(cursor=pCursor, loadTag=False): diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index ca835ea6..18dfdbc5 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -381,7 +381,7 @@ class GuiDocViewer(QTextBrowser): userCursor = self.textCursor() userSelection = userCursor.hasSelection() - mnuContext = QMenu() + mnuContext = QMenu(self) # Cut, Copy and Paste # =================== diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 4d804c40..0742ab54 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -228,7 +228,7 @@ class GuiNovelToolBar(QWidget): self.tbRefresh.clicked.connect(self._refreshNovelTree) # More Options Menu - self.mMore = QMenu() + self.mMore = QMenu(self) self.mLastCol = self.mMore.addMenu(self.tr("Last Column")) self.gLastCol = QActionGroup(self.mMore) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index e753a828..929f37c6 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -31,8 +31,8 @@ from enum import Enum from time import time from typing import TYPE_CHECKING -from PyQt5.QtGui import QDropEvent, QMouseEvent, QPalette -from PyQt5.QtCore import QPoint, Qt, QSize, pyqtSignal, pyqtSlot +from PyQt5.QtGui import QDragMoveEvent, QDropEvent, QMouseEvent, QPalette +from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( QAbstractItemView, QDialog, QFrame, QHBoxLayout, QHeaderView, QLabel, QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, @@ -248,7 +248,7 @@ class GuiProjectToolBar(QWidget): self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) # Quick Links - self.mQuick = QMenu() + self.mQuick = QMenu(self) self.tbQuick = QToolButton(self) self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links")) @@ -269,7 +269,7 @@ class GuiProjectToolBar(QWidget): self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1)) # Add Item Menu - self.mAdd = QMenu() + self.mAdd = QMenu(self) self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"])) self.aAddEmpty.triggered.connect( @@ -307,7 +307,7 @@ class GuiProjectToolBar(QWidget): self.tbAdd.setPopupMode(QToolButton.InstantPopup) # More Options Menu - self.mMore = QMenu() + self.mMore = QMenu(self) self.aExpand = self.mMore.addAction(self.tr("Expand All")) self.aExpand.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, True)) @@ -507,8 +507,15 @@ class GuiProjectTree(QTreeWidget): # Allow Move by Drag & Drop self.setDragEnabled(True) self.setDragDropMode(QAbstractItemView.InternalMove) + self.setDropIndicatorShown(True) + + # Disable built-in autoscroll as it isn't working in some Qt + # releases (see #1561) and instead use our own implementation + self.setAutoScroll(False) # But don't allow drop on root level + # Due to a bug, this stops working somewhere between Qt 5.15.3 + # and 5.15.8, so this is also blocked in dropEvent (see #1569) trRoot = self.invisibleRootItem() trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled) @@ -524,6 +531,13 @@ class GuiProjectTree(QTreeWidget): self.itemDoubleClicked.connect(self._treeDoubleClick) self.itemSelectionChanged.connect(self._treeSelectionChange) + # Autoscroll + self._scrollMargin = SHARED.theme.baseIconSize + self._scrollDirection = 0 + self._scrollTimer = QTimer() + self._scrollTimer.timeout.connect(self._doAutoScroll) + self._scrollTimer.setInterval(250) + # Set custom settings self.initSettings() @@ -1196,7 +1210,7 @@ class GuiProjectTree(QTreeWidget): logger.debug("No item found") return False - ctxMenu = QMenu() + ctxMenu = QMenu(self) # Trash Folder # ============ @@ -1343,6 +1357,17 @@ class GuiProjectTree(QTreeWidget): return True + @pyqtSlot() + def _doAutoScroll(self) -> None: + """Scroll one item up or down based on direction value.""" + if self._scrollDirection == -1: + self.scrollToItem(self.itemAbove(self.itemAt(1, 1))) + elif self._scrollDirection == 1: + self.scrollToItem(self.itemBelow(self.itemAt(1, self.height() - 1))) + self._scrollDirection = 0 + self._scrollTimer.stop() + return + ## # Events ## @@ -1374,14 +1399,36 @@ class GuiProjectTree(QTreeWidget): return + def dragMoveEvent(self, event: QDragMoveEvent) -> None: + """Capture the drag move event to enable edge autoscroll.""" + y = event.pos().y() + if y < self._scrollMargin: + if not self._scrollTimer.isActive(): + self._scrollDirection = -1 + self._scrollTimer.start() + elif y > self.height() - self._scrollMargin: + if not self._scrollTimer.isActive(): + self._scrollDirection = 1 + self._scrollTimer.start() + super().dragMoveEvent(event) + return + def dropEvent(self, event: QDropEvent) -> None: """Overload the drop item event to ensure relevant data has been updated. """ sHandle = self.getSelectedHandle() sItem = self._getTreeItem(sHandle) if sHandle else None - if sHandle is None or sItem is None: + if sHandle is None or sItem is None or sItem.parent() is None: logger.error("Invalid drag and drop event") + event.ignore() + return + + if not self.indexAt(event.pos()).isValid(): + # Needed due to a bug somewhere around Qt 5.15.8 that + # ignores the invisible root item flags + logger.error("Invalid drop location") + event.ignore() return logger.debug("Drag'n'drop of item '%s' accepted", sHandle) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index a5c55fd7..4d324677 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -991,6 +991,7 @@ class GuiMain(QMainWindow): if dlgWords.result() == QDialog.Accepted: logger.debug("Reloading word list") SHARED.updateSpellCheckLanguage(reload=True) + self.docEditor.spellCheckDocument() return True diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 842114a9..cfa1b038 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -332,14 +332,16 @@ class _FilterTab(QWidget): treeHeader = self.optTree.header() treeHeader.setStretchLastSection(False) + treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1551 treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.Stretch) treeHeader.setSectionResizeMode(self.C_ACTIVE, QHeaderView.Fixed) treeHeader.setSectionResizeMode(self.C_STATUS, QHeaderView.Fixed) treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg) treeHeader.resizeSection(self.C_STATUS, iPx + cMg) - self.optTree.setSelectionMode(QAbstractItemView.ExtendedSelection) self.optTree.setDragDropMode(QAbstractItemView.NoDragDrop) + self.optTree.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.optTree.setSelectionBehavior(QAbstractItemView.SelectRows) # Filters # ======= @@ -390,11 +392,11 @@ class _FilterTab(QWidget): self.mainSplit.addWidget(self.filterOpt) self.mainSplit.setCollapsible(0, False) self.mainSplit.setCollapsible(1, False) - self.mainSplit.setStretchFactor(0, 0) - self.mainSplit.setStretchFactor(1, 1) + self.mainSplit.setStretchFactor(0, 1) + self.mainSplit.setStretchFactor(1, 0) self.mainSplit.setSizes([ - CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 1)), - CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 1)) + CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 300)), + CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 300)) ]) self.outerBox = QHBoxLayout() @@ -718,7 +720,7 @@ class _HeadingsTab(QWidget): self.formSyntax = _HeadingSyntaxHighlighter(self.editTextBox.document()) - self.menuInsert = QMenu() + self.menuInsert = QMenu(self) self.aInsTitle = self.menuInsert.addAction(self.tr("Title")) self.aInsChNum = self.menuInsert.addAction(self.tr("Chapter Number")) self.aInsChWord = self.menuInsert.addAction(self.tr("Chapter Number (Word)")) diff --git a/pkgutils.py b/pkgutils.py index 840ffef7..99f77521 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -325,6 +325,7 @@ def buildQtI18nTS(sysArgs): from PyQt6.lupdate import lupdate except ImportError: print("ERROR: This command requires lupdate from PyQt6") + print("On Debian/Ubuntu, install: pyqt6-dev-tools") sys.exit(1) print("") diff --git a/sample/content/f1471bef9f2ae.nwd b/sample/content/f1471bef9f2ae.nwd index 8ed84dde..9253d784 100644 --- a/sample/content/f1471bef9f2ae.nwd +++ b/sample/content/f1471bef9f2ae.nwd @@ -1,12 +1,10 @@ %%~name: Space %%~path: 15c4492bd5107/f1471bef9f2ae %%~kind: WORLD/NOTE -%%~hash: bc139a020239c9c0d936aa9d0bcc3644ae2f370c -%%~date: Unknown/2023-08-25 16:52:06 +%%~hash: b08c9796fd9918a87e107bf26606e75341a49ce8 +%%~date: Unknown/2023-11-03 00:36:58 # Space @tag: Space Space … it’s an awful lot of nothing, with bits in it here and there. Some of which, people like to call home. - - diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 0f953f81..2b29444b 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -166,21 +166,21 @@ def testBaseConfig_Localisation(fncPath, tstPaths): i18nDir = fncPath / "i18n" i18nDir.mkdir() - tstConf._nwLangPath = str(i18nDir) + tstConf._nwLangPath = i18nDir copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm") writeFile(i18nDir / "nw_en_GB.ts", "") writeFile(i18nDir / "nw_abcd.qm", "") tstApp = MockApp() - tstConf.initLocalisation(tstApp) + tstConf.initLocalisation(tstApp) # type: ignore # Check Lists theList = tstConf.listLanguages(tstConf.LANG_NW) assert theList == [("en_GB", "British English")] theList = tstConf.listLanguages(tstConf.LANG_PROJ) assert theList == [("en_GB", "British English")] - theList = tstConf.listLanguages(None) + theList = tstConf.listLanguages(None) # type: ignore assert theList == [] # Add Language diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py index 6c7dee61..771040ab 100644 --- a/tests/test_core/test_core_tohtml.py +++ b/tests/test_core/test_core_tohtml.py @@ -29,177 +29,176 @@ from novelwriter.core.project import NWProject @pytest.mark.core def testCoreToHtml_ConvertFormat(mockGUI): - """Test the tokenizer and converter chain using the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) + """Test the tokenizer and converter chain using the ToHtml class.""" + project = NWProject() + html = ToHtml(project) # Novel Files Headers # =================== - theHtml._isNovel = True - theHtml._isNote = False - theHtml._isFirst = True + html._isNovel = True + html._isNote = False + html._isFirst = True # Header 1 - theHtml._text = "# Partition\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "# Partition\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Partition

\n" ) # Header 2 - theHtml._text = "## Chapter Title\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "## Chapter Title\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Chapter Title

\n" ) # Header 3 - theHtml._text = "### Scene Title\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Scene Title

\n" + html._text = "### Scene Title\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Scene Title

\n" # Header 4 - theHtml._text = "#### Section Title\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Section Title

\n" + html._text = "#### Section Title\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Section Title

\n" # Title - theHtml._text = "#! Title\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "#! Title\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Title

\n" ) # Unnumbered - theHtml._text = "##! Prologue\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Prologue

\n" + html._text = "##! Prologue\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Prologue

\n" # Note Files Headers # ================== - theHtml._isNovel = False - theHtml._isNote = True - theHtml._isFirst = True - theHtml.setLinkHeaders(True) + html._isNovel = False + html._isNote = True + html._isFirst = True + html.setLinkHeaders(True) # Header 1 - theHtml._text = "# Heading One\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading One

\n" + html._text = "# Heading One\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading One

\n" # Header 2 - theHtml._text = "## Heading Two\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading Two

\n" + html._text = "## Heading Two\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading Two

\n" # Header 3 - theHtml._text = "### Heading Three\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading Three

\n" + html._text = "### Heading Three\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading Three

\n" # Header 4 - theHtml._text = "#### Heading Four\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading Four

\n" + html._text = "#### Heading Four\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading Four

\n" # Title - theHtml._text = "#! Heading One\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( - "

Heading One

\n" + html._text = "#! Heading One\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( + "

Heading One

\n" ) # Unnumbered - theHtml._text = "##! Heading Two\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "

Heading Two

\n" + html._text = "##! Heading Two\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "

Heading Two

\n" # Paragraphs # ========== # Text - theHtml._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Some nested bold and italic and " "strikethrough text here

\n" ) # Text w/Hard Break - theHtml._text = "Line one \nLine two \nLine three\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Line one \nLine two \nLine three\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Line one
Line two
Line three

\n" ) # Synopsis - theHtml._text = "%synopsis: The synopsis ...\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "" + html._text = "%synopsis: The synopsis ...\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "" - theHtml.setSynopsis(True) - theHtml._text = "%synopsis: The synopsis ...\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setSynopsis(True) + html._text = "%synopsis: The synopsis ...\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Synopsis: The synopsis ...

\n" ) # Comment - theHtml._text = "% A comment ...\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "" + html._text = "% A comment ...\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "" - theHtml.setComments(True) - theHtml._text = "% A comment ...\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setComments(True) + html._text = "% A comment ...\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Comment: A comment ...

\n" ) # Keywords - theHtml._text = "@char: Bod, Jane\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == "" + html._text = "@char: Bod, Jane\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == "" - theHtml.setKeywords(True) - theHtml._text = "@char: Bod, Jane\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setKeywords(True) + html._text = "@char: Bod, Jane\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Characters: " "Bod, Jane

\n" ) # Multiple Keywords - theHtml.setKeywords(True) - theHtml._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setKeywords(True) + html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

" - "Chapter

\n" + "Chapter\n" "

" "Point of View: Bod" "

\n" @@ -214,13 +213,13 @@ def testCoreToHtml_ConvertFormat(mockGUI): # Preview Mode # ============ - theHtml.setPreview(True, True) + html.setPreview(True, True) # Text (HTML4) - theHtml._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Some nested bold and italic and " "strikethrough " "text here

\n" @@ -231,109 +230,108 @@ def testCoreToHtml_ConvertFormat(mockGUI): @pytest.mark.core def testCoreToHtml_ConvertDirect(mockGUI): - """Test the converter directly using the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) + """Test the converter directly using the ToHtml class.""" + project = NWProject() + html = ToHtml(project) - theHtml._isNovel = True - theHtml._isNote = False - theHtml.setLinkHeaders(True) + html._isNovel = True + html._isNote = False + html.setLinkHeaders(True) # Special Titles # ============== # Title - theHtml._tokens = [ - (theHtml.T_TITLE, 1, "A Title", None, theHtml.A_PBB | theHtml.A_CENTRE), - (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_TITLE, 1, "A Title", None, html.A_PBB | html.A_CENTRE), + (html.T_EMPTY, 1, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

" - "A Title

\n" + "A Title\n" ) # Unnumbered - theHtml._tokens = [ - (theHtml.T_UNNUM, 1, "Prologue", None, theHtml.A_PBB), - (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_UNNUM, 1, "Prologue", None, html.A_PBB), + (html.T_EMPTY, 1, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

" - "Prologue

\n" + "Prologue\n" ) # Separators # ========== # Separator - theHtml._tokens = [ - (theHtml.T_SEP, 1, "* * *", None, theHtml.A_CENTRE), - (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_SEP, 1, "* * *", None, html.A_CENTRE), + (html.T_EMPTY, 1, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == "

* * *

\n" + html.doConvert() + assert html.theResult == "

* * *

\n" # Skip - theHtml._tokens = [ - (theHtml.T_SKIP, 1, "", None, theHtml.A_NONE), - (theHtml.T_EMPTY, 1, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_SKIP, 1, "", None, html.A_NONE), + (html.T_EMPTY, 1, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == "

 

\n" + html.doConvert() + assert html.theResult == "

 

\n" # Alignment # ========= - theHtml.setLinkHeaders(False) + html.setLinkHeaders(False) # Align Left - theHtml.setStyles(False) - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT), + html.setStyles(False) + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_LEFT), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) - theHtml.setStyles(True) + html.setStyles(True) # Align Left - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_LEFT), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_LEFT), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) # Align Right - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_RIGHT), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_RIGHT), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) # Align Centre - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_CENTRE), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_CENTRE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) # Align Justify - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_JUSTIFY), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_JUSTIFY), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) @@ -341,11 +339,11 @@ def testCoreToHtml_ConvertDirect(mockGUI): # ========== # Page Break Always - theHtml._tokens = [ - (theHtml.T_HEAD1, 1, "A Title", None, theHtml.A_PBB | theHtml.A_PBA), + html._tokens = [ + (html.T_HEAD1, 1, "A Title", None, html.A_PBB | html.A_PBA), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

A Title

\n" ) @@ -354,22 +352,22 @@ def testCoreToHtml_ConvertDirect(mockGUI): # ====== # Indent Left - theHtml._tokens = [ - (theHtml.T_TEXT, 1, "Some text ...", [], theHtml.A_IND_L), - (theHtml.T_EMPTY, 2, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_TEXT, 1, "Some text ...", [], html.A_IND_L), + (html.T_EMPTY, 2, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

Some text ...

\n" ) # Indent Right - theHtml._tokens = [ - (theHtml.T_TEXT, 1, "Some text ...", [], theHtml.A_IND_R), - (theHtml.T_EMPTY, 2, "", None, theHtml.A_NONE), + html._tokens = [ + (html.T_TEXT, 1, "Some text ...", [], html.A_IND_R), + (html.T_EMPTY, 2, "", None, html.A_NONE), ] - theHtml.doConvert() - assert theHtml.theResult == ( + html.doConvert() + assert html.theResult == ( "

Some text ...

\n" ) @@ -378,40 +376,39 @@ def testCoreToHtml_ConvertDirect(mockGUI): @pytest.mark.core def testCoreToHtml_SpecialCases(mockGUI): - """Test some special cases that have caused errors in the past. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) - theHtml._isNovel = True + """Test some special cases that have caused errors in the past.""" + project = NWProject() + html = ToHtml(project) + html._isNovel = True # Greater/Lesser than symbols # =========================== - theHtml._text = "Text with > and < with some **bold text** in it.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Text with > and < with some **bold text** in it.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Text with > and < with some bold text in it.

\n" ) - theHtml._text = "Text with some <**bold text**> in it.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Text with some <**bold text**> in it.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Text with some <bold text> in it.

\n" ) - theHtml._text = "Let's > be > _difficult **shall** > we_?\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Let's > be > _difficult **shall** > we_?\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Let's > be > difficult shall > we?

\n" ) - theHtml._text = "Test > text _<**bold**>_ and more.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Test > text _<**bold**>_ and more.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Test > text <bold> and more.

\n" ) @@ -419,20 +416,20 @@ def testCoreToHtml_SpecialCases(mockGUI): # =================== # See: https://github.com/vkbo/novelWriter/issues/950 - theHtml.setComments(True) - theHtml._text = "% Test > text _<**bold**>_ and more.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html.setComments(True) + html._text = "% Test > text _<**bold**>_ and more.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

" "Comment: Test > text _<**bold**>_ and more." "

\n" ) - theHtml._text = "## Heading <1>\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "## Heading <1>\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Heading <1>

\n" ) @@ -440,10 +437,10 @@ def testCoreToHtml_SpecialCases(mockGUI): # ==================== # See: https://github.com/vkbo/novelWriter/issues/1412 - theHtml._text = "Test text \\**_bold_** and more.\n" - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = "Test text \\**_bold_** and more.\n" + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Test text **bold** and more.

\n" ) @@ -452,11 +449,10 @@ def testCoreToHtml_SpecialCases(mockGUI): @pytest.mark.core def testCoreToHtml_Complex(mockGUI, fncPath): - """Test the save method of the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) - theHtml._isNovel = True + """Test the save method of the ToHtml class.""" + project = NWProject() + html = ToHtml(project) + html._isNovel = True # Build Project # ============= @@ -502,21 +498,21 @@ def testCoreToHtml_Complex(mockGUI, fncPath): ] for i in range(len(docText)): - theHtml._text = docText[i] - theHtml.doPreProcessing() - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == resText[i] + html._text = docText[i] + html.doPreProcessing() + html.tokenizeText() + html.doConvert() + assert html.theResult == resText[i] - assert theHtml.fullHTML == resText + assert html.fullHTML == resText - theHtml.replaceTabs(nSpaces=2, spaceChar=" ") + html.replaceTabs(nSpaces=2, spaceChar=" ") resText[6] = "

A Section

\n

  More text in scene two.

\n" # Check File # ========== - theStyle = theHtml.getStyleSheet() + theStyle = html.getStyleSheet() htmlDoc = ( "\n" "\n" @@ -539,7 +535,7 @@ def testCoreToHtml_Complex(mockGUI, fncPath): ) saveFile = fncPath / "outFile.htm" - theHtml.saveHtml5(saveFile) + html.saveHtml5(saveFile) assert readFile(saveFile) == htmlDoc # END Test testCoreToHtml_Complex @@ -547,86 +543,84 @@ def testCoreToHtml_Complex(mockGUI, fncPath): @pytest.mark.core def testCoreToHtml_Methods(mockGUI): - """Test all the other methods of the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) - theHtml.setKeepMarkdown(True) + """Test all the other methods of the ToHtml class.""" + project = NWProject() + html = ToHtml(project) + html.setKeepMarkdown(True) # Auto-Replace, keep Unicode docText = "Text with & short–dash, long—dash …\n" - theHtml._text = docText - theHtml.setReplaceUnicode(False) - theHtml.doPreProcessing() - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = docText + html.setReplaceUnicode(False) + html.doPreProcessing() + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Text with <brackets> & short–dash, long—dash …

\n" ) # Auto-Replace, replace Unicode docText = "Text with & short–dash, long—dash …\n" - theHtml._text = docText - theHtml.setReplaceUnicode(True) - theHtml.doPreProcessing() - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theResult == ( + html._text = docText + html.setReplaceUnicode(True) + html.doPreProcessing() + html.tokenizeText() + html.doConvert() + assert html.theResult == ( "

Text with <brackets> & short–dash, long—dash …

\n" ) # With Preview - theHtml.setPreview(True, True) - theHtml._text = docText - theHtml.doPreProcessing() - theHtml.tokenizeText() - theHtml.doConvert() - assert theHtml.theMarkdown[-1] == ( + html.setPreview(True, True) + html._text = docText + html.doPreProcessing() + html.tokenizeText() + html.doConvert() + assert html.theMarkdown[-1] == ( "Text with & short–dash, long—dash …\n\n" ) # Result Size - assert theHtml.getFullResultSize() == 147 + assert html.getFullResultSize() == 147 # CSS # === - assert len(theHtml.getStyleSheet()) > 1 - assert "p {text-align: left;" in " ".join(theHtml.getStyleSheet()) - assert "p {text-align: justify;" not in " ".join(theHtml.getStyleSheet()) + assert len(html.getStyleSheet()) > 1 + assert "p {text-align: left;" in " ".join(html.getStyleSheet()) + assert "p {text-align: justify;" not in " ".join(html.getStyleSheet()) - theHtml.setJustify(True) - assert "p {text-align: left;" not in " ".join(theHtml.getStyleSheet()) - assert "p {text-align: justify;" in " ".join(theHtml.getStyleSheet()) + html.setJustify(True) + assert "p {text-align: left;" not in " ".join(html.getStyleSheet()) + assert "p {text-align: justify;" in " ".join(html.getStyleSheet()) - theHtml.setStyles(False) - assert theHtml.getStyleSheet() == [] + html.setStyles(False) + assert html.getStyleSheet() == [] # END Test testCoreToHtml_Methods @pytest.mark.core def testCoreToHtml_Format(mockGUI): - """Test all the formatters for the ToHtml class. - """ - theProject = NWProject() - theHtml = ToHtml(theProject) + """Test all the formatters for the ToHtml class.""" + project = NWProject() + html = ToHtml(project) # Export Mode # =========== - assert theHtml._formatSynopsis("synopsis text") == ( + assert html._formatSynopsis("synopsis text") == ( "

Synopsis: synopsis text

\n" ) - assert theHtml._formatComments("comment text") == ( + assert html._formatComments("comment text") == ( "

Comment: comment text

\n" ) - assert theHtml._formatKeywords("") == "" - assert theHtml._formatKeywords("tag: Jane") == ( + assert html._formatKeywords("") == "" + assert html._formatKeywords("tag: Jane") == ( "Tag: Jane" ) - assert theHtml._formatKeywords("char: Bod, Jane") == ( + assert html._formatKeywords("char: Bod, Jane") == ( "Characters: " "Bod, " "Jane" @@ -635,20 +629,20 @@ def testCoreToHtml_Format(mockGUI): # Preview Mode # ============ - theHtml.setPreview(True, True) + html.setPreview(True, True) - assert theHtml._formatSynopsis("synopsis text") == ( + assert html._formatSynopsis("synopsis text") == ( "

Synopsis: synopsis text

\n" ) - assert theHtml._formatComments("comment text") == ( + assert html._formatComments("comment text") == ( "

comment text

\n" ) - assert theHtml._formatKeywords("") == "" - assert theHtml._formatKeywords("tag: Jane") == ( + assert html._formatKeywords("") == "" + assert html._formatKeywords("tag: Jane") == ( "Tag: Jane" ) - assert theHtml._formatKeywords("char: Bod, Jane") == ( + assert html._formatKeywords("char: Bod, Jane") == ( "Characters: " "Bod, " "Jane" diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index 2db60f85..df5590b3 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -442,8 +442,8 @@ def testCoreToken_MetaFormat(mockGUI): tokens._text = "% A comment\n" tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_COMMENT, 1, "A comment", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_COMMENT, 0, "A comment", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "\n" @@ -455,14 +455,14 @@ def testCoreToken_MetaFormat(mockGUI): tokens._text = "%synopsis: The synopsis\n" tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_SYNOPSIS, 1, "The synopsis", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] tokens._text = "% synopsis: The synopsis\n" tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_SYNOPSIS, 1, "The synopsis", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SYNOPSIS, 0, "The synopsis", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "\n" @@ -474,8 +474,8 @@ def testCoreToken_MetaFormat(mockGUI): tokens._text = "@char: Bod\n" tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_KEYWORD, 1, "char: Bod", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_KEYWORD, 0, "char: Bod", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "\n" @@ -489,10 +489,10 @@ def testCoreToken_MetaFormat(mockGUI): styMid = Tokenizer.A_NONE | Tokenizer.A_Z_BTMMRG | Tokenizer.A_Z_TOPMRG styBtm = Tokenizer.A_NONE | Tokenizer.A_Z_TOPMRG assert tokens._tokens == [ - (Tokenizer.T_KEYWORD, 1, "pov: Bod", None, styTop), - (Tokenizer.T_KEYWORD, 2, "plot: Main", None, styMid), - (Tokenizer.T_KEYWORD, 3, "location: Europe", None, styBtm), - (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), + (Tokenizer.T_KEYWORD, 0, "pov: Bod", None, styTop), + (Tokenizer.T_KEYWORD, 0, "plot: Main", None, styMid), + (Tokenizer.T_KEYWORD, 0, "location: Europe", None, styBtm), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n" @@ -521,23 +521,23 @@ def testCoreToken_MarginFormat(mockGUI): ) tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_TEXT, 1, "Some regular text", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 3, "Some left-aligned text", [], Tokenizer.A_LEFT), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some right-aligned text", [], Tokenizer.A_RIGHT), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 7, "Some centered text", [], Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 9, "Left-indented block", [], Tokenizer.A_IND_L), - (Tokenizer.T_EMPTY, 10, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 11, "Right-indented block", [], Tokenizer.A_IND_R), - (Tokenizer.T_EMPTY, 12, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 13, "Double-indented block", [], dblIndent), - (Tokenizer.T_EMPTY, 14, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 15, "Right-indent, right-aligned", [], rIndAlign), - (Tokenizer.T_EMPTY, 16, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 16, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some regular text", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some left-aligned text", [], Tokenizer.A_LEFT), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some right-aligned text", [], Tokenizer.A_RIGHT), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some centered text", [], Tokenizer.A_CENTRE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Left-indented block", [], Tokenizer.A_IND_L), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Right-indented block", [], Tokenizer.A_IND_R), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Double-indented block", [], dblIndent), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Right-indent, right-aligned", [], rIndAlign), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == ( "Some regular text\n\n" @@ -655,20 +655,20 @@ def testCoreToken_TextFormat(mockGUI): tokens._text = "Some plain text\non two lines\n\n\n" tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_TEXT, 1, "Some plain text", [], Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 2, "on two lines", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "Some plain text", [], Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 0, "on two lines", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "Some plain text\non two lines\n\n\n\n" tokens.setBodyText(False) tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_EMPTY, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "\n\n\n" tokens.setBodyText(True) @@ -678,7 +678,7 @@ def testCoreToken_TextFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some bolded text on this lines", [ (5, Tokenizer.FMT_B_B), @@ -686,7 +686,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "Some **bolded text** on this lines\n\n" @@ -694,7 +694,7 @@ def testCoreToken_TextFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some italic text on this lines", [ (5, Tokenizer.FMT_I_B), @@ -702,7 +702,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "Some _italic text_ on this lines\n\n" @@ -710,7 +710,7 @@ def testCoreToken_TextFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some bold italic text on this lines", [ (5, Tokenizer.FMT_B_B), @@ -720,7 +720,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "Some **_bold italic text_** on this lines\n\n" @@ -728,7 +728,7 @@ def testCoreToken_TextFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some strikethrough text on this lines", [ (5, Tokenizer.FMT_D_B), @@ -736,7 +736,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == "Some ~~strikethrough text~~ on this lines\n\n" @@ -744,7 +744,7 @@ def testCoreToken_TextFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ ( - Tokenizer.T_TEXT, 1, + Tokenizer.T_TEXT, 0, "Some nested bold and italic and strikethrough text here", [ (5, Tokenizer.FMT_B_B), @@ -756,7 +756,7 @@ def testCoreToken_TextFormat(mockGUI): ], Tokenizer.A_NONE ), - (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 0, "", None, Tokenizer.A_NONE), ] assert tokens.theMarkdown[-1] == ( "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n\n" @@ -778,11 +778,11 @@ def testCoreToken_SpecialFormat(mockGUI): correctResp = [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_CENTRE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_HEAD1, 2, "Title Two", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_HEAD1, 5, "Title Two", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), ] # Command wo/Space @@ -826,12 +826,12 @@ def testCoreToken_SpecialFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Multiple Empty Paragraphs @@ -846,12 +846,12 @@ def testCoreToken_SpecialFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Three Skips @@ -863,14 +863,14 @@ def testCoreToken_SpecialFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 3, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Malformed Command, Case 1 @@ -882,11 +882,11 @@ def testCoreToken_SpecialFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Malformed Command, Case 2 @@ -898,11 +898,11 @@ def testCoreToken_SpecialFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Malformed Command, Case 3 @@ -914,11 +914,11 @@ def testCoreToken_SpecialFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 5, "Some text to go here ...", [], Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Empty Paragraph and Page Break @@ -934,13 +934,13 @@ def testCoreToken_SpecialFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_PBB), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 7, "Some text to go here ...", [], 0), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_PBB), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # Multiple Skip @@ -953,15 +953,15 @@ def testCoreToken_SpecialFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ (Tokenizer.T_HEAD1, 1, "Title One", None, Tokenizer.A_PBB | Tokenizer.A_CENTRE), - (Tokenizer.T_EMPTY, 2, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 4, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_PBB), - (Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_NONE), - (Tokenizer.T_SKIP, 5, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 6, "", None, Tokenizer.A_NONE), - (Tokenizer.T_TEXT, 7, "Some text to go here ...", [], 0), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), - (Tokenizer.T_EMPTY, 8, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_PBB), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_SKIP, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_TEXT, 1, "Some text to go here ...", [], 0), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), + (Tokenizer.T_EMPTY, 1, "", None, Tokenizer.A_NONE), ] # END Test testCoreToken_SpecialFormat