novelWriter 2.5.3__py3-none-any.whl → 2.6b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/METADATA +1 -1
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/RECORD +80 -60
- novelwriter/__init__.py +49 -10
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
- novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
- novelwriter/assets/icons/typicons_light/icons.conf +8 -0
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
- novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/common.py +100 -2
- novelwriter/config.py +25 -15
- novelwriter/constants.py +168 -60
- novelwriter/core/buildsettings.py +66 -39
- novelwriter/core/coretools.py +145 -147
- novelwriter/core/docbuild.py +132 -170
- novelwriter/core/index.py +38 -37
- novelwriter/core/item.py +41 -8
- novelwriter/core/itemmodel.py +518 -0
- novelwriter/core/options.py +4 -1
- novelwriter/core/project.py +67 -89
- novelwriter/core/spellcheck.py +9 -14
- novelwriter/core/status.py +7 -5
- novelwriter/core/tree.py +268 -287
- novelwriter/dialogs/docmerge.py +7 -17
- novelwriter/dialogs/preferences.py +46 -33
- novelwriter/dialogs/projectsettings.py +5 -5
- novelwriter/enum.py +36 -23
- novelwriter/extensions/configlayout.py +27 -12
- novelwriter/extensions/modified.py +13 -1
- novelwriter/extensions/pagedsidebar.py +5 -5
- novelwriter/formats/shared.py +155 -0
- novelwriter/formats/todocx.py +1191 -0
- novelwriter/formats/tohtml.py +451 -0
- novelwriter/{core → formats}/tokenizer.py +487 -491
- novelwriter/formats/tomarkdown.py +217 -0
- novelwriter/{core → formats}/toodt.py +311 -432
- novelwriter/formats/toqdoc.py +484 -0
- novelwriter/formats/toraw.py +91 -0
- novelwriter/gui/doceditor.py +342 -284
- novelwriter/gui/dochighlight.py +96 -84
- novelwriter/gui/docviewer.py +88 -31
- novelwriter/gui/docviewerpanel.py +17 -25
- novelwriter/gui/editordocument.py +17 -2
- novelwriter/gui/itemdetails.py +25 -28
- novelwriter/gui/mainmenu.py +129 -63
- novelwriter/gui/noveltree.py +45 -47
- novelwriter/gui/outline.py +196 -249
- novelwriter/gui/projtree.py +594 -1241
- novelwriter/gui/search.py +9 -10
- novelwriter/gui/sidebar.py +7 -6
- novelwriter/gui/theme.py +10 -5
- novelwriter/guimain.py +100 -196
- novelwriter/shared.py +66 -27
- novelwriter/text/counting.py +2 -0
- novelwriter/text/patterns.py +168 -60
- novelwriter/tools/manusbuild.py +14 -12
- novelwriter/tools/manuscript.py +120 -78
- novelwriter/tools/manussettings.py +424 -291
- novelwriter/tools/welcome.py +4 -4
- novelwriter/tools/writingstats.py +3 -3
- novelwriter/types.py +23 -7
- novelwriter/core/tohtml.py +0 -530
- novelwriter/core/tomarkdown.py +0 -252
- novelwriter/core/toqdoc.py +0 -419
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +0 -0
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/top_level.txt +0 -0
novelwriter/core/docbuild.py
CHANGED
@@ -35,13 +35,15 @@ from novelwriter.constants import nwLabels
|
|
35
35
|
from novelwriter.core.buildsettings import BuildSettings
|
36
36
|
from novelwriter.core.item import NWItem
|
37
37
|
from novelwriter.core.project import NWProject
|
38
|
-
from novelwriter.core.tohtml import ToHtml
|
39
|
-
from novelwriter.core.tokenizer import Tokenizer
|
40
|
-
from novelwriter.core.tomarkdown import ToMarkdown
|
41
|
-
from novelwriter.core.toodt import ToOdt
|
42
|
-
from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument
|
43
38
|
from novelwriter.enum import nwBuildFmt
|
44
39
|
from novelwriter.error import formatException, logException
|
40
|
+
from novelwriter.formats.todocx import ToDocX
|
41
|
+
from novelwriter.formats.tohtml import ToHtml
|
42
|
+
from novelwriter.formats.tokenizer import Tokenizer
|
43
|
+
from novelwriter.formats.tomarkdown import ToMarkdown
|
44
|
+
from novelwriter.formats.toodt import ToOdt
|
45
|
+
from novelwriter.formats.toqdoc import ToQTextDocument
|
46
|
+
from novelwriter.formats.toraw import ToRaw
|
45
47
|
|
46
48
|
logger = logging.getLogger(__name__)
|
47
49
|
|
@@ -85,20 +87,6 @@ class NWBuildDocument:
|
|
85
87
|
"""
|
86
88
|
return self._cache
|
87
89
|
|
88
|
-
##
|
89
|
-
# Setters
|
90
|
-
##
|
91
|
-
|
92
|
-
def setCountEnabled(self, state: bool) -> None:
|
93
|
-
"""Turn on/off stats for builds."""
|
94
|
-
self._count = state
|
95
|
-
return
|
96
|
-
|
97
|
-
def setBuildOutline(self, state: bool) -> None:
|
98
|
-
"""Turn on/off outline for builds."""
|
99
|
-
self._outline = state
|
100
|
-
return
|
101
|
-
|
102
90
|
##
|
103
91
|
# Special Methods
|
104
92
|
##
|
@@ -125,182 +113,117 @@ class NWBuildDocument:
|
|
125
113
|
self._queue.append(item.itemHandle)
|
126
114
|
return
|
127
115
|
|
128
|
-
def iterBuildPreview(self,
|
116
|
+
def iterBuildPreview(self, newPage: bool) -> Iterable[tuple[int, bool]]:
|
129
117
|
"""Build a preview QTextDocument."""
|
130
118
|
makeObj = ToQTextDocument(self._project)
|
131
119
|
filtered = self._setupBuild(makeObj)
|
132
|
-
|
133
|
-
self._outline = True
|
134
|
-
self._count = True
|
135
|
-
|
136
|
-
font = QFont()
|
137
|
-
font.fromString(self._build.getStr("format.textFont"))
|
138
|
-
|
139
|
-
makeObj.initDocument(font, theme)
|
140
|
-
for i, tHandle in enumerate(self._queue):
|
141
|
-
self._error = None
|
142
|
-
if filtered.get(tHandle, (False, 0))[0]:
|
143
|
-
yield i, self._doBuild(makeObj, tHandle)
|
144
|
-
else:
|
145
|
-
yield i, False
|
146
|
-
|
147
|
-
makeObj.appendFootnotes()
|
148
|
-
|
149
|
-
self._error = None
|
150
|
-
self._cache = makeObj
|
151
|
-
|
152
|
-
return
|
153
|
-
|
154
|
-
def iterBuild(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
|
155
|
-
"""Wrapper for builders based on format."""
|
156
|
-
if bFormat in (nwBuildFmt.ODT, nwBuildFmt.FODT):
|
157
|
-
yield from self.iterBuildOpenDocument(path, bFormat == nwBuildFmt.FODT)
|
158
|
-
elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
|
159
|
-
yield from self.iterBuildHTML(path, asJson=bFormat == nwBuildFmt.J_HTML)
|
160
|
-
elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
|
161
|
-
yield from self.iterBuildMarkdown(path, bFormat == nwBuildFmt.EXT_MD)
|
162
|
-
elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
|
163
|
-
yield from self.iterBuildNWD(path, asJson=bFormat == nwBuildFmt.J_NWD)
|
164
|
-
return
|
165
|
-
|
166
|
-
def iterBuildOpenDocument(self, path: Path, isFlat: bool) -> Iterable[tuple[int, bool]]:
|
167
|
-
"""Build an Open Document file."""
|
168
|
-
makeObj = ToOdt(self._project, isFlat=isFlat)
|
169
|
-
filtered = self._setupBuild(makeObj)
|
170
120
|
makeObj.initDocument()
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
if filtered.get(tHandle, (False, 0))[0]:
|
175
|
-
yield i, self._doBuild(makeObj, tHandle)
|
176
|
-
else:
|
177
|
-
yield i, False
|
178
|
-
|
121
|
+
makeObj.setShowNewPage(newPage)
|
122
|
+
self._outline = True
|
123
|
+
yield from self._iterBuild(makeObj, filtered)
|
179
124
|
makeObj.closeDocument()
|
180
|
-
|
181
125
|
self._error = None
|
182
126
|
self._cache = makeObj
|
183
|
-
|
184
|
-
try:
|
185
|
-
if isFlat:
|
186
|
-
makeObj.saveFlatXML(path)
|
187
|
-
else:
|
188
|
-
makeObj.saveOpenDocText(path)
|
189
|
-
except Exception as exc:
|
190
|
-
logException()
|
191
|
-
self._error = formatException(exc)
|
192
|
-
|
193
127
|
return
|
194
128
|
|
195
|
-
def
|
196
|
-
"""
|
197
|
-
is used for generating build previews.
|
198
|
-
"""
|
199
|
-
makeObj = ToHtml(self._project)
|
200
|
-
filtered = self._setupBuild(makeObj)
|
201
|
-
|
202
|
-
for i, tHandle in enumerate(self._queue):
|
203
|
-
self._error = None
|
204
|
-
if filtered.get(tHandle, (False, 0))[0]:
|
205
|
-
yield i, self._doBuild(makeObj, tHandle)
|
206
|
-
else:
|
207
|
-
yield i, False
|
208
|
-
|
209
|
-
makeObj.appendFootnotes()
|
210
|
-
|
211
|
-
if not self._build.getBool("html.preserveTabs"):
|
212
|
-
makeObj.replaceTabs()
|
213
|
-
|
129
|
+
def iterBuildDocument(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
|
130
|
+
"""Wrapper for builders based on format."""
|
214
131
|
self._error = None
|
215
|
-
self._cache =
|
216
|
-
|
217
|
-
if isinstance(path, Path):
|
218
|
-
try:
|
219
|
-
if asJson:
|
220
|
-
makeObj.saveHtmlJson(path)
|
221
|
-
else:
|
222
|
-
makeObj.saveHtml5(path)
|
223
|
-
except Exception as exc:
|
224
|
-
logException()
|
225
|
-
self._error = formatException(exc)
|
132
|
+
self._cache = None
|
226
133
|
|
227
|
-
|
134
|
+
if bFormat in (nwBuildFmt.J_HTML, nwBuildFmt.J_NWD):
|
135
|
+
# Ensure that JSON output has the correct extension
|
136
|
+
path = path.with_suffix(".json")
|
228
137
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
138
|
+
if bFormat in (nwBuildFmt.ODT, nwBuildFmt.FODT):
|
139
|
+
makeObj = ToOdt(self._project, bFormat == nwBuildFmt.FODT)
|
140
|
+
filtered = self._setupBuild(makeObj)
|
141
|
+
makeObj.initDocument()
|
142
|
+
yield from self._iterBuild(makeObj, filtered)
|
143
|
+
makeObj.closeDocument()
|
233
144
|
|
234
|
-
|
235
|
-
|
236
|
-
|
145
|
+
elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
|
146
|
+
makeObj = ToHtml(self._project)
|
147
|
+
filtered = self._setupBuild(makeObj)
|
148
|
+
makeObj.initDocument()
|
149
|
+
yield from self._iterBuild(makeObj, filtered)
|
150
|
+
makeObj.closeDocument()
|
151
|
+
if not self._build.getBool("html.preserveTabs"):
|
152
|
+
makeObj.replaceTabs()
|
237
153
|
|
238
|
-
|
239
|
-
self.
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
154
|
+
elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
|
155
|
+
makeObj = ToMarkdown(self._project, bFormat == nwBuildFmt.EXT_MD)
|
156
|
+
filtered = self._setupBuild(makeObj)
|
157
|
+
yield from self._iterBuild(makeObj, filtered)
|
158
|
+
makeObj.closeDocument()
|
159
|
+
if self._build.getBool("format.replaceTabs"):
|
160
|
+
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
244
161
|
|
245
|
-
|
162
|
+
elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
|
163
|
+
makeObj = ToRaw(self._project)
|
164
|
+
filtered = self._setupBuild(makeObj)
|
165
|
+
yield from self._iterBuild(makeObj, filtered)
|
166
|
+
makeObj.closeDocument()
|
167
|
+
if self._build.getBool("format.replaceTabs"):
|
168
|
+
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
169
|
+
|
170
|
+
elif bFormat == nwBuildFmt.DOCX:
|
171
|
+
makeObj = ToDocX(self._project)
|
172
|
+
filtered = self._setupBuild(makeObj)
|
173
|
+
makeObj.initDocument()
|
174
|
+
yield from self._iterBuild(makeObj, filtered)
|
175
|
+
makeObj.closeDocument()
|
176
|
+
|
177
|
+
elif bFormat == nwBuildFmt.PDF:
|
178
|
+
makeObj = ToQTextDocument(self._project)
|
179
|
+
makeObj.disableAnchors()
|
180
|
+
filtered = self._setupBuild(makeObj)
|
181
|
+
makeObj.initDocument(pdf=True)
|
182
|
+
yield from self._iterBuild(makeObj, filtered)
|
183
|
+
makeObj.closeDocument()
|
184
|
+
|
185
|
+
else:
|
186
|
+
logger.error("Unsupported document format")
|
187
|
+
return
|
246
188
|
|
247
189
|
self._error = None
|
248
190
|
self._cache = makeObj
|
249
191
|
|
250
192
|
try:
|
251
|
-
makeObj.
|
193
|
+
makeObj.saveDocument(path)
|
252
194
|
except Exception as exc:
|
253
195
|
logException()
|
254
196
|
self._error = formatException(exc)
|
255
197
|
|
256
198
|
return
|
257
199
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
filtered = self._setupBuild(makeObj)
|
262
|
-
|
263
|
-
makeObj.setKeepMarkdown(True)
|
200
|
+
##
|
201
|
+
# Internal Functions
|
202
|
+
##
|
264
203
|
|
204
|
+
def _iterBuild(self, makeObj: Tokenizer, filtered: dict) -> Iterable[tuple[int, bool]]:
|
205
|
+
"""Iterate over buildable documents."""
|
206
|
+
self._count = True
|
265
207
|
for i, tHandle in enumerate(self._queue):
|
266
208
|
self._error = None
|
267
209
|
if filtered.get(tHandle, (False, 0))[0]:
|
268
|
-
yield i, self._doBuild(makeObj, tHandle
|
210
|
+
yield i, self._doBuild(makeObj, tHandle)
|
269
211
|
else:
|
270
212
|
yield i, False
|
271
|
-
|
272
|
-
if self._build.getBool("format.replaceTabs"):
|
273
|
-
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
274
|
-
|
275
|
-
self._error = None
|
276
|
-
self._cache = makeObj
|
277
|
-
|
278
|
-
if isinstance(path, Path):
|
279
|
-
try:
|
280
|
-
if asJson:
|
281
|
-
makeObj.saveRawMarkdownJSON(path)
|
282
|
-
else:
|
283
|
-
makeObj.saveRawMarkdown(path)
|
284
|
-
except Exception as exc:
|
285
|
-
logException()
|
286
|
-
self._error = formatException(exc)
|
287
|
-
|
288
213
|
return
|
289
214
|
|
290
|
-
##
|
291
|
-
# Internal Functions
|
292
|
-
##
|
293
|
-
|
294
215
|
def _setupBuild(self, bldObj: Tokenizer) -> dict:
|
295
216
|
"""Configure the build object."""
|
296
217
|
# Get Settings
|
297
218
|
textFont = QFont(CONFIG.textFont)
|
298
219
|
textFont.fromString(self._build.getStr("format.textFont"))
|
299
|
-
bldObj.setFont(textFont)
|
300
220
|
|
301
|
-
bldObj.
|
302
|
-
|
303
|
-
|
221
|
+
bldObj.setTextFont(textFont)
|
222
|
+
bldObj.setLanguage(self._project.data.language)
|
223
|
+
|
224
|
+
bldObj.setPartitionFormat(
|
225
|
+
self._build.getStr("headings.fmtPart"),
|
226
|
+
self._build.getBool("headings.hidePart")
|
304
227
|
)
|
305
228
|
bldObj.setChapterFormat(
|
306
229
|
self._build.getStr("headings.fmtChapter"),
|
@@ -326,6 +249,10 @@ class NWBuildDocument:
|
|
326
249
|
self._build.getBool("headings.centerTitle"),
|
327
250
|
self._build.getBool("headings.breakTitle")
|
328
251
|
)
|
252
|
+
bldObj.setPartitionStyle(
|
253
|
+
self._build.getBool("headings.centerPart"),
|
254
|
+
self._build.getBool("headings.breakPart")
|
255
|
+
)
|
329
256
|
bldObj.setChapterStyle(
|
330
257
|
self._build.getBool("headings.centerChapter"),
|
331
258
|
self._build.getBool("headings.breakChapter")
|
@@ -338,12 +265,46 @@ class NWBuildDocument:
|
|
338
265
|
bldObj.setJustify(self._build.getBool("format.justifyText"))
|
339
266
|
bldObj.setLineHeight(self._build.getFloat("format.lineHeight"))
|
340
267
|
bldObj.setKeepLineBreaks(self._build.getBool("format.keepBreaks"))
|
341
|
-
bldObj.
|
268
|
+
bldObj.setDialogHighlight(self._build.getBool("format.showDialogue"))
|
342
269
|
bldObj.setFirstLineIndent(
|
343
270
|
self._build.getBool("format.firstLineIndent"),
|
344
271
|
self._build.getFloat("format.firstIndentWidth"),
|
345
272
|
self._build.getBool("format.indentFirstPar"),
|
346
273
|
)
|
274
|
+
bldObj.setHeadingStyles(
|
275
|
+
self._build.getBool("doc.colorHeadings"),
|
276
|
+
self._build.getBool("doc.scaleHeadings"),
|
277
|
+
self._build.getBool("doc.boldHeadings"),
|
278
|
+
)
|
279
|
+
|
280
|
+
bldObj.setTitleMargins(
|
281
|
+
self._build.getFloat("format.titleMarginT"),
|
282
|
+
self._build.getFloat("format.titleMarginB"),
|
283
|
+
)
|
284
|
+
bldObj.setHead1Margins(
|
285
|
+
self._build.getFloat("format.h1MarginT"),
|
286
|
+
self._build.getFloat("format.h1MarginB"),
|
287
|
+
)
|
288
|
+
bldObj.setHead2Margins(
|
289
|
+
self._build.getFloat("format.h2MarginT"),
|
290
|
+
self._build.getFloat("format.h2MarginB"),
|
291
|
+
)
|
292
|
+
bldObj.setHead3Margins(
|
293
|
+
self._build.getFloat("format.h3MarginT"),
|
294
|
+
self._build.getFloat("format.h3MarginB"),
|
295
|
+
)
|
296
|
+
bldObj.setHead4Margins(
|
297
|
+
self._build.getFloat("format.h4MarginT"),
|
298
|
+
self._build.getFloat("format.h4MarginB"),
|
299
|
+
)
|
300
|
+
bldObj.setTextMargins(
|
301
|
+
self._build.getFloat("format.textMarginT"),
|
302
|
+
self._build.getFloat("format.textMarginB"),
|
303
|
+
)
|
304
|
+
bldObj.setSeparatorMargins(
|
305
|
+
self._build.getFloat("format.sepMarginT"),
|
306
|
+
self._build.getFloat("format.sepMarginB"),
|
307
|
+
)
|
347
308
|
|
348
309
|
bldObj.setBodyText(self._build.getBool("text.includeBodyText"))
|
349
310
|
bldObj.setSynopsis(self._build.getBool("text.includeSynopsis"))
|
@@ -355,13 +316,13 @@ class NWBuildDocument:
|
|
355
316
|
bldObj.setStyles(self._build.getBool("html.addStyles"))
|
356
317
|
bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))
|
357
318
|
|
358
|
-
if isinstance(bldObj, ToOdt):
|
359
|
-
bldObj.setColourHeaders(self._build.getBool("odt.addColours"))
|
360
|
-
bldObj.setLanguage(self._project.data.language)
|
319
|
+
if isinstance(bldObj, (ToOdt, ToDocX)):
|
361
320
|
bldObj.setHeaderFormat(
|
362
|
-
self._build.getStr("
|
321
|
+
self._build.getStr("doc.pageHeader"),
|
322
|
+
self._build.getInt("doc.pageCountOffset"),
|
363
323
|
)
|
364
324
|
|
325
|
+
if isinstance(bldObj, (ToOdt, ToDocX, ToQTextDocument)):
|
365
326
|
scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
|
366
327
|
pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
|
367
328
|
bldObj.setPageLayout(
|
@@ -384,14 +345,17 @@ class NWBuildDocument:
|
|
384
345
|
tItem = self._project.tree[tHandle]
|
385
346
|
if isinstance(tItem, NWItem):
|
386
347
|
try:
|
387
|
-
if tItem.isRootType()
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
348
|
+
if tItem.isRootType():
|
349
|
+
if tItem.isNovelLike():
|
350
|
+
bldObj.setBreakNext()
|
351
|
+
else:
|
352
|
+
bldObj.addRootHeading(tHandle)
|
353
|
+
if convert:
|
354
|
+
bldObj.doConvert()
|
355
|
+
if self._count:
|
356
|
+
bldObj.countStats()
|
357
|
+
if self._outline:
|
358
|
+
bldObj.buildOutline()
|
395
359
|
elif tItem.isFileType():
|
396
360
|
bldObj.setText(tHandle)
|
397
361
|
bldObj.doPreProcessing()
|
@@ -402,8 +366,6 @@ class NWBuildDocument:
|
|
402
366
|
bldObj.buildOutline()
|
403
367
|
if convert:
|
404
368
|
bldObj.doConvert()
|
405
|
-
else:
|
406
|
-
logger.info(f"Build: Skipping '{tHandle}'")
|
407
369
|
|
408
370
|
except Exception:
|
409
371
|
self._error = f"Build: Failed to build '{tHandle}'"
|
novelwriter/core/index.py
CHANGED
@@ -40,7 +40,7 @@ from novelwriter import SHARED
|
|
40
40
|
from novelwriter.common import (
|
41
41
|
checkInt, isHandle, isItemClass, isListInstance, isTitleTag, jsonEncode
|
42
42
|
)
|
43
|
-
from novelwriter.constants import nwFiles,
|
43
|
+
from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
|
44
44
|
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType
|
45
45
|
from novelwriter.error import logException
|
46
46
|
from novelwriter.text.counting import standardCounter
|
@@ -116,24 +116,24 @@ class NWIndex:
|
|
116
116
|
# Public Methods
|
117
117
|
##
|
118
118
|
|
119
|
-
def
|
119
|
+
def clear(self) -> None:
|
120
120
|
"""Clear the index dictionaries and time stamps."""
|
121
121
|
self._tagsIndex.clear()
|
122
122
|
self._itemIndex.clear()
|
123
123
|
self._indexChange = 0.0
|
124
124
|
self._rootChange = {}
|
125
|
-
SHARED.
|
125
|
+
SHARED.emitIndexCleared(self._project)
|
126
126
|
return
|
127
127
|
|
128
|
-
def
|
128
|
+
def rebuild(self) -> None:
|
129
129
|
"""Rebuild the entire index from scratch."""
|
130
|
-
self.
|
130
|
+
self.clear()
|
131
131
|
for nwItem in self._project.tree:
|
132
132
|
if nwItem.isFileType():
|
133
133
|
text = self._project.storage.getDocumentText(nwItem.itemHandle)
|
134
134
|
self.scanText(nwItem.itemHandle, text, blockSignal=True)
|
135
135
|
self._indexBroken = False
|
136
|
-
SHARED.
|
136
|
+
SHARED.emitIndexAvailable(self._project)
|
137
137
|
return
|
138
138
|
|
139
139
|
def deleteHandle(self, tHandle: str) -> None:
|
@@ -143,10 +143,7 @@ class NWIndex:
|
|
143
143
|
for tTag in delTags:
|
144
144
|
del self._tagsIndex[tTag]
|
145
145
|
del self._itemIndex[tHandle]
|
146
|
-
SHARED.
|
147
|
-
"event": "updateTags",
|
148
|
-
"deleted": delTags,
|
149
|
-
})
|
146
|
+
SHARED.emitIndexChangedTags(self._project, [], delTags)
|
150
147
|
return
|
151
148
|
|
152
149
|
def reIndexHandle(self, tHandle: str | None) -> None:
|
@@ -212,7 +209,7 @@ class NWIndex:
|
|
212
209
|
self.reIndexHandle(fHandle)
|
213
210
|
|
214
211
|
self._indexChange = time()
|
215
|
-
SHARED.
|
212
|
+
SHARED.emitIndexAvailable(self._project)
|
216
213
|
|
217
214
|
logger.debug("Index loaded in %.3f ms", (time() - tStart)*1000)
|
218
215
|
|
@@ -296,10 +293,7 @@ class NWIndex:
|
|
296
293
|
self._indexChange = nowTime
|
297
294
|
self._rootChange[tItem.itemRoot] = nowTime
|
298
295
|
if not blockSignal:
|
299
|
-
|
300
|
-
"event": "scanText",
|
301
|
-
"handle": tHandle,
|
302
|
-
})
|
296
|
+
tItem.notifyToRefresh()
|
303
297
|
|
304
298
|
return True
|
305
299
|
|
@@ -370,11 +364,7 @@ class NWIndex:
|
|
370
364
|
del self._tagsIndex[tTag]
|
371
365
|
deleted.append(tTag)
|
372
366
|
if updated or deleted:
|
373
|
-
SHARED.
|
374
|
-
"event": "updateTags",
|
375
|
-
"updated": updated,
|
376
|
-
"deleted": deleted,
|
377
|
-
})
|
367
|
+
SHARED.emitIndexChangedTags(self._project, updated, deleted)
|
378
368
|
|
379
369
|
return
|
380
370
|
|
@@ -487,13 +477,14 @@ class NWIndex:
|
|
487
477
|
if nBits == 0:
|
488
478
|
return []
|
489
479
|
|
490
|
-
# Check that the
|
491
|
-
|
480
|
+
# Check that the keyword is valid
|
481
|
+
kBit = tBits[0]
|
482
|
+
isGood[0] = kBit in nwKeyWords.VALID_KEYS
|
492
483
|
if not isGood[0] or nBits == 1:
|
493
484
|
return isGood
|
494
485
|
|
495
486
|
# For a tag, only the first value is accepted, the rest are ignored
|
496
|
-
if
|
487
|
+
if kBit == nwKeyWords.TAG_KEY and nBits > 1:
|
497
488
|
check, _ = self.parseValue(tBits[1])
|
498
489
|
if check in self._tagsIndex:
|
499
490
|
isGood[1] = self._tagsIndex.tagHandle(check) == tHandle
|
@@ -501,12 +492,16 @@ class NWIndex:
|
|
501
492
|
isGood[1] = True
|
502
493
|
return isGood
|
503
494
|
|
495
|
+
if kBit == nwKeyWords.MENTION_KEY and nBits > 1:
|
496
|
+
isGood[1:nBits] = [aBit in self._tagsIndex for aBit in tBits[1:nBits]]
|
497
|
+
return isGood
|
498
|
+
|
504
499
|
# If we're still here, we check that the references exist
|
505
500
|
# Class references cannot have the | symbol in them
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
501
|
+
if rClass := nwKeyWords.KEY_CLASS.get(kBit):
|
502
|
+
for n in range(1, nBits):
|
503
|
+
if (aBit := tBits[n]) in self._tagsIndex:
|
504
|
+
isGood[n] = self._tagsIndex.tagClass(aBit) == rClass.name and "|" not in aBit
|
510
505
|
|
511
506
|
return isGood
|
512
507
|
|
@@ -569,7 +564,7 @@ class NWIndex:
|
|
569
564
|
for _, _, hItem in self._itemIndex.iterNovelStructure(
|
570
565
|
rHandle=rootHandle, activeOnly=activeOnly
|
571
566
|
):
|
572
|
-
iLevel =
|
567
|
+
iLevel = nwStyles.H_LEVEL.get(hItem.level, 0)
|
573
568
|
hCount[iLevel] += 1
|
574
569
|
return hCount
|
575
570
|
|
@@ -591,7 +586,7 @@ class NWIndex:
|
|
591
586
|
rHandle=rHandle, activeOnly=activeOnly
|
592
587
|
):
|
593
588
|
tKey = f"{tHandle}:{sTitle}"
|
594
|
-
iLevel =
|
589
|
+
iLevel = nwStyles.H_LEVEL.get(hItem.level, 0)
|
595
590
|
if iLevel > maxDepth:
|
596
591
|
if pKey in tData:
|
597
592
|
tData[pKey]["words"] += hItem.wordCount
|
@@ -635,7 +630,7 @@ class NWIndex:
|
|
635
630
|
"""Extract all references made in a file, and optionally title
|
636
631
|
section.
|
637
632
|
"""
|
638
|
-
tRefs = {x: [] for x in nwKeyWords.
|
633
|
+
tRefs = {x: [] for x in nwKeyWords.VALID_KEYS}
|
639
634
|
for rTitle, hItem in self._itemIndex.iterItemHeaders(tHandle):
|
640
635
|
if sTitle is None or sTitle == rTitle:
|
641
636
|
for aTag, refTypes in hItem.references.items():
|
@@ -681,9 +676,10 @@ class NWIndex:
|
|
681
676
|
"""Return all tags used by a specific document."""
|
682
677
|
return self._itemIndex.allItemTags(tHandle) if tHandle else []
|
683
678
|
|
684
|
-
def getClassTags(self, itemClass: nwItemClass) -> list[str]:
|
679
|
+
def getClassTags(self, itemClass: nwItemClass | None) -> list[str]:
|
685
680
|
"""Return all tags based on itemClass."""
|
686
|
-
|
681
|
+
name = None if itemClass is None else itemClass.name
|
682
|
+
return self._tagsIndex.filterTagNames(name)
|
687
683
|
|
688
684
|
def getTagsData(
|
689
685
|
self, activeOnly: bool = True
|
@@ -780,11 +776,16 @@ class TagsIndex:
|
|
780
776
|
"""Get the class of a given tag."""
|
781
777
|
return self._tags.get(tagKey.lower(), {}).get("class", None)
|
782
778
|
|
783
|
-
def filterTagNames(self, className: str) -> list[str]:
|
779
|
+
def filterTagNames(self, className: str | None) -> list[str]:
|
784
780
|
"""Get a list of tag names for a given class."""
|
785
|
-
|
786
|
-
|
787
|
-
|
781
|
+
if className is None:
|
782
|
+
return [
|
783
|
+
x.get("name", "") for x in self._tags.values()
|
784
|
+
]
|
785
|
+
else:
|
786
|
+
return [
|
787
|
+
x.get("name", "") for x in self._tags.values() if x.get("class", "") == className
|
788
|
+
]
|
788
789
|
|
789
790
|
##
|
790
791
|
# Pack/Unpack
|
@@ -1251,7 +1252,7 @@ class IndexHeading:
|
|
1251
1252
|
|
1252
1253
|
def setLevel(self, level: str) -> None:
|
1253
1254
|
"""Set the level of the heading if it's a valid value."""
|
1254
|
-
if level in
|
1255
|
+
if level in nwStyles.H_VALID:
|
1255
1256
|
self._level = level
|
1256
1257
|
return
|
1257
1258
|
|