30 |
35 |
31 class PreviewerHTML(QWidget): |
36 class PreviewerHTML(QWidget): |
32 """ |
37 """ |
33 Class implementing a previewer widget for HTML, Markdown and ReST files. |
38 Class implementing a previewer widget for HTML, Markdown and ReST files. |
34 """ |
39 """ |
|
40 |
35 def __init__(self, parent=None): |
41 def __init__(self, parent=None): |
36 """ |
42 """ |
37 Constructor |
43 Constructor |
38 |
44 |
39 @param parent reference to the parent widget (QWidget) |
45 @param parent reference to the parent widget (QWidget) |
40 """ |
46 """ |
41 super().__init__(parent) |
47 super().__init__(parent) |
42 |
48 |
43 self.__layout = QVBoxLayout(self) |
49 self.__layout = QVBoxLayout(self) |
44 |
50 |
45 self.titleLabel = QLabel(self) |
51 self.titleLabel = QLabel(self) |
46 self.titleLabel.setWordWrap(True) |
52 self.titleLabel.setWordWrap(True) |
47 self.titleLabel.setTextInteractionFlags( |
53 self.titleLabel.setTextInteractionFlags( |
48 Qt.TextInteractionFlag.NoTextInteraction) |
54 Qt.TextInteractionFlag.NoTextInteraction |
|
55 ) |
49 self.__layout.addWidget(self.titleLabel) |
56 self.__layout.addWidget(self.titleLabel) |
50 |
57 |
51 self.__previewAvailable = True |
58 self.__previewAvailable = True |
52 |
59 |
53 try: |
60 try: |
54 from PyQt6.QtWebEngineWidgets import QWebEngineView |
61 from PyQt6.QtWebEngineWidgets import QWebEngineView |
|
62 |
55 self.previewView = QWebEngineView(self) |
63 self.previewView = QWebEngineView(self) |
56 self.previewView.page().linkHovered.connect(self.__showLink) |
64 self.previewView.page().linkHovered.connect(self.__showLink) |
57 except ImportError: |
65 except ImportError: |
58 self.__previewAvailable = False |
66 self.__previewAvailable = False |
59 self.titleLabel.setText(self.tr( |
67 self.titleLabel.setText( |
60 "<b>HTML Preview is not available!<br/>" |
68 self.tr( |
61 "Install PyQt6-WebEngine.</b>")) |
69 "<b>HTML Preview is not available!<br/>" |
|
70 "Install PyQt6-WebEngine.</b>" |
|
71 ) |
|
72 ) |
62 self.titleLabel.setAlignment(Qt.AlignmentFlag.AlignHCenter) |
73 self.titleLabel.setAlignment(Qt.AlignmentFlag.AlignHCenter) |
63 self.__layout.addStretch() |
74 self.__layout.addStretch() |
64 return |
75 return |
65 |
76 |
66 sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, |
77 sizePolicy = QSizePolicy( |
67 QSizePolicy.Policy.Expanding) |
78 QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding |
|
79 ) |
68 sizePolicy.setHorizontalStretch(0) |
80 sizePolicy.setHorizontalStretch(0) |
69 sizePolicy.setVerticalStretch(0) |
81 sizePolicy.setVerticalStretch(0) |
70 sizePolicy.setHeightForWidth( |
82 sizePolicy.setHeightForWidth(self.previewView.sizePolicy().hasHeightForWidth()) |
71 self.previewView.sizePolicy().hasHeightForWidth()) |
|
72 self.previewView.setSizePolicy(sizePolicy) |
83 self.previewView.setSizePolicy(sizePolicy) |
73 self.previewView.setContextMenuPolicy( |
84 self.previewView.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) |
74 Qt.ContextMenuPolicy.NoContextMenu) |
|
75 self.previewView.setUrl(QUrl("about:blank")) |
85 self.previewView.setUrl(QUrl("about:blank")) |
76 self.__layout.addWidget(self.previewView) |
86 self.__layout.addWidget(self.previewView) |
77 |
87 |
78 self.jsCheckBox = QCheckBox(self.tr("Enable JavaScript"), self) |
88 self.jsCheckBox = QCheckBox(self.tr("Enable JavaScript"), self) |
79 self.jsCheckBox.setToolTip(self.tr( |
89 self.jsCheckBox.setToolTip( |
80 "Select to enable JavaScript for HTML previews")) |
90 self.tr("Select to enable JavaScript for HTML previews") |
|
91 ) |
81 self.__layout.addWidget(self.jsCheckBox) |
92 self.__layout.addWidget(self.jsCheckBox) |
82 |
93 |
83 self.ssiCheckBox = QCheckBox(self.tr("Enable Server Side Includes"), |
94 self.ssiCheckBox = QCheckBox(self.tr("Enable Server Side Includes"), self) |
84 self) |
95 self.ssiCheckBox.setToolTip( |
85 self.ssiCheckBox.setToolTip(self.tr( |
96 self.tr("Select to enable support for Server Side Includes") |
86 "Select to enable support for Server Side Includes")) |
97 ) |
87 self.__layout.addWidget(self.ssiCheckBox) |
98 self.__layout.addWidget(self.ssiCheckBox) |
88 |
99 |
89 self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked) |
100 self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked) |
90 self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked) |
101 self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked) |
91 self.previewView.titleChanged.connect(self.on_previewView_titleChanged) |
102 self.previewView.titleChanged.connect(self.on_previewView_titleChanged) |
92 |
103 |
93 self.jsCheckBox.setChecked( |
104 self.jsCheckBox.setChecked(Preferences.getUI("ShowFilePreviewJS")) |
94 Preferences.getUI("ShowFilePreviewJS")) |
105 self.ssiCheckBox.setChecked(Preferences.getUI("ShowFilePreviewSSI")) |
95 self.ssiCheckBox.setChecked( |
106 |
96 Preferences.getUI("ShowFilePreviewSSI")) |
|
97 |
|
98 self.__scrollBarPositions = {} |
107 self.__scrollBarPositions = {} |
99 self.__vScrollBarAtEnd = {} |
108 self.__vScrollBarAtEnd = {} |
100 self.__hScrollBarAtEnd = {} |
109 self.__hScrollBarAtEnd = {} |
101 |
110 |
102 self.__processingThread = PreviewProcessingThread() |
111 self.__processingThread = PreviewProcessingThread() |
103 self.__processingThread.htmlReady.connect(self.__setHtml) |
112 self.__processingThread.htmlReady.connect(self.__setHtml) |
104 |
113 |
105 self.__previewedPath = None |
114 self.__previewedPath = None |
106 self.__previewedEditor = None |
115 self.__previewedEditor = None |
107 |
116 |
108 def shutdown(self): |
117 def shutdown(self): |
109 """ |
118 """ |
110 Public method to perform shutdown actions. |
119 Public method to perform shutdown actions. |
111 """ |
120 """ |
112 if self.__previewAvailable: |
121 if self.__previewAvailable: |
113 self.__processingThread.wait() |
122 self.__processingThread.wait() |
114 |
123 |
115 @pyqtSlot(bool) |
124 @pyqtSlot(bool) |
116 def on_jsCheckBox_clicked(self, checked): |
125 def on_jsCheckBox_clicked(self, checked): |
117 """ |
126 """ |
118 Private slot to enable/disable JavaScript. |
127 Private slot to enable/disable JavaScript. |
119 |
128 |
120 @param checked state of the checkbox (boolean) |
129 @param checked state of the checkbox (boolean) |
121 """ |
130 """ |
122 Preferences.setUI("ShowFilePreviewJS", checked) |
131 Preferences.setUI("ShowFilePreviewJS", checked) |
123 self.__setJavaScriptEnabled(checked) |
132 self.__setJavaScriptEnabled(checked) |
124 |
133 |
125 def __setJavaScriptEnabled(self, enable): |
134 def __setJavaScriptEnabled(self, enable): |
126 """ |
135 """ |
127 Private method to enable/disable JavaScript. |
136 Private method to enable/disable JavaScript. |
128 |
137 |
129 @param enable flag indicating the enable state (boolean) |
138 @param enable flag indicating the enable state (boolean) |
130 """ |
139 """ |
131 self.jsCheckBox.setChecked(enable) |
140 self.jsCheckBox.setChecked(enable) |
132 |
141 |
133 settings = self.previewView.settings() |
142 settings = self.previewView.settings() |
134 settings.setAttribute(settings.JavascriptEnabled, enable) |
143 settings.setAttribute(settings.JavascriptEnabled, enable) |
135 |
144 |
136 self.processEditor() |
145 self.processEditor() |
137 |
146 |
138 @pyqtSlot(bool) |
147 @pyqtSlot(bool) |
139 def on_ssiCheckBox_clicked(self, checked): |
148 def on_ssiCheckBox_clicked(self, checked): |
140 """ |
149 """ |
141 Private slot to enable/disable SSI. |
150 Private slot to enable/disable SSI. |
142 |
151 |
143 @param checked state of the checkbox (boolean) |
152 @param checked state of the checkbox (boolean) |
144 """ |
153 """ |
145 Preferences.setUI("ShowFilePreviewSSI", checked) |
154 Preferences.setUI("ShowFilePreviewSSI", checked) |
146 self.processEditor() |
155 self.processEditor() |
147 |
156 |
148 @pyqtSlot(str) |
157 @pyqtSlot(str) |
149 def __showLink(self, urlStr): |
158 def __showLink(self, urlStr): |
150 """ |
159 """ |
151 Private slot to show the hovered link in a tooltip. |
160 Private slot to show the hovered link in a tooltip. |
152 |
161 |
153 @param urlStr hovered URL |
162 @param urlStr hovered URL |
154 @type str |
163 @type str |
155 """ |
164 """ |
156 QToolTip.showText(QCursor.pos(), urlStr, self.previewView) |
165 QToolTip.showText(QCursor.pos(), urlStr, self.previewView) |
157 |
166 |
158 def processEditor(self, editor=None): |
167 def processEditor(self, editor=None): |
159 """ |
168 """ |
160 Public slot to process an editor's text. |
169 Public slot to process an editor's text. |
161 |
170 |
162 @param editor editor to be processed (Editor) |
171 @param editor editor to be processed (Editor) |
163 """ |
172 """ |
164 if not self.__previewAvailable: |
173 if not self.__previewAvailable: |
165 return |
174 return |
166 |
175 |
167 if editor is None: |
176 if editor is None: |
168 editor = self.__previewedEditor |
177 editor = self.__previewedEditor |
169 else: |
178 else: |
170 self.__previewedEditor = editor |
179 self.__previewedEditor = editor |
171 |
180 |
172 if editor is not None: |
181 if editor is not None: |
173 fn = editor.getFileName() |
182 fn = editor.getFileName() |
174 |
183 |
175 if fn: |
184 if fn: |
176 extension = os.path.normcase(os.path.splitext(fn)[1][1:]) |
185 extension = os.path.normcase(os.path.splitext(fn)[1][1:]) |
177 else: |
186 else: |
178 extension = "" |
187 extension = "" |
179 if ( |
188 if ( |
180 extension in Preferences.getEditor( |
189 extension in Preferences.getEditor("PreviewHtmlFileNameExtensions") |
181 "PreviewHtmlFileNameExtensions") or |
190 or editor.getLanguage() == "HTML" |
182 editor.getLanguage() == "HTML" |
|
183 ): |
191 ): |
184 language = "HTML" |
192 language = "HTML" |
185 elif ( |
193 elif ( |
186 extension in Preferences.getEditor( |
194 extension in Preferences.getEditor("PreviewMarkdownFileNameExtensions") |
187 "PreviewMarkdownFileNameExtensions") or |
195 or editor.getLanguage().lower() == "markdown" |
188 editor.getLanguage().lower() == "markdown" |
|
189 ): |
196 ): |
190 language = "Markdown" |
197 language = "Markdown" |
191 elif ( |
198 elif ( |
192 extension in Preferences.getEditor( |
199 extension in Preferences.getEditor("PreviewRestFileNameExtensions") |
193 "PreviewRestFileNameExtensions") or |
200 or editor.getLanguage().lower() == "restructuredtext" |
194 editor.getLanguage().lower() == "restructuredtext" |
|
195 ): |
201 ): |
196 language = "ReST" |
202 language = "ReST" |
197 else: |
203 else: |
198 self.__setHtml(fn, self.tr( |
204 self.__setHtml( |
199 "<p>No preview available for this type of file.</p>")) |
205 fn, self.tr("<p>No preview available for this type of file.</p>") |
|
206 ) |
200 return |
207 return |
201 |
208 |
202 if fn: |
209 if fn: |
203 rootPath = os.path.dirname(os.path.abspath(fn)) |
210 rootPath = os.path.dirname(os.path.abspath(fn)) |
204 else: |
211 else: |
205 rootPath = "" |
212 rootPath = "" |
206 |
213 |
207 if bool(editor.text()): |
214 if bool(editor.text()): |
208 self.__processingThread.process( |
215 self.__processingThread.process( |
209 fn, language, editor.text(), |
216 fn, |
210 self.ssiCheckBox.isChecked(), rootPath, |
217 language, |
|
218 editor.text(), |
|
219 self.ssiCheckBox.isChecked(), |
|
220 rootPath, |
211 Preferences.getEditor("PreviewRestUseSphinx"), |
221 Preferences.getEditor("PreviewRestUseSphinx"), |
212 Preferences.getEditor("PreviewMarkdownNLtoBR"), |
222 Preferences.getEditor("PreviewMarkdownNLtoBR"), |
213 Preferences.getEditor( |
223 Preferences.getEditor("PreviewMarkdownUsePyMdownExtensions"), |
214 "PreviewMarkdownUsePyMdownExtensions"), |
|
215 Preferences.getEditor("PreviewMarkdownHTMLFormat"), |
224 Preferences.getEditor("PreviewMarkdownHTMLFormat"), |
216 Preferences.getEditor("PreviewRestDocutilsHTMLFormat")) |
225 Preferences.getEditor("PreviewRestDocutilsHTMLFormat"), |
|
226 ) |
217 |
227 |
218 def __setHtml(self, filePath, html, rootPath): |
228 def __setHtml(self, filePath, html, rootPath): |
219 """ |
229 """ |
220 Private method to set the HTML to the view and restore the scroll bars |
230 Private method to set the HTML to the view and restore the scroll bars |
221 positions. |
231 positions. |
222 |
232 |
223 @param filePath file path of the previewed editor |
233 @param filePath file path of the previewed editor |
224 @type str |
234 @type str |
225 @param html processed HTML text ready to be shown |
235 @param html processed HTML text ready to be shown |
226 @type str |
236 @type str |
227 @param rootPath path of the web site root |
237 @param rootPath path of the web site root |
228 @type str |
238 @type str |
229 """ |
239 """ |
230 self.__previewedPath = Utilities.normcasepath( |
240 self.__previewedPath = Utilities.normcasepath( |
231 Utilities.fromNativeSeparators(filePath)) |
241 Utilities.fromNativeSeparators(filePath) |
|
242 ) |
232 self.__saveScrollBarPositions() |
243 self.__saveScrollBarPositions() |
233 self.previewView.page().loadFinished.connect( |
244 self.previewView.page().loadFinished.connect(self.__restoreScrollBarPositions) |
234 self.__restoreScrollBarPositions) |
|
235 if not filePath: |
245 if not filePath: |
236 filePath = "/" |
246 filePath = "/" |
237 baseUrl = ( |
247 baseUrl = ( |
238 QUrl.fromLocalFile(rootPath + "/index.html") |
248 QUrl.fromLocalFile(rootPath + "/index.html") |
239 if rootPath else |
249 if rootPath |
240 QUrl.fromLocalFile(filePath) |
250 else QUrl.fromLocalFile(filePath) |
241 ) |
251 ) |
242 self.previewView.setHtml(html, baseUrl=baseUrl) |
252 self.previewView.setHtml(html, baseUrl=baseUrl) |
243 if self.__previewedEditor: |
253 if self.__previewedEditor: |
244 self.__previewedEditor.setFocus() |
254 self.__previewedEditor.setFocus() |
245 |
255 |
246 @pyqtSlot(str) |
256 @pyqtSlot(str) |
247 def on_previewView_titleChanged(self, title): |
257 def on_previewView_titleChanged(self, title): |
248 """ |
258 """ |
249 Private slot to handle a change of the title. |
259 Private slot to handle a change of the title. |
250 |
260 |
251 @param title new title (string) |
261 @param title new title (string) |
252 """ |
262 """ |
253 if title: |
263 if title: |
254 self.titleLabel.setText(self.tr("Preview - {0}").format(title)) |
264 self.titleLabel.setText(self.tr("Preview - {0}").format(title)) |
255 else: |
265 else: |
256 self.titleLabel.setText(self.tr("Preview")) |
266 self.titleLabel.setText(self.tr("Preview")) |
257 |
267 |
258 def __saveScrollBarPositions(self): |
268 def __saveScrollBarPositions(self): |
259 """ |
269 """ |
260 Private method to save scroll bar positions for a previewed editor. |
270 Private method to save scroll bar positions for a previewed editor. |
261 """ |
271 """ |
262 from PyQt6.QtCore import QPoint |
272 from PyQt6.QtCore import QPoint |
|
273 |
263 try: |
274 try: |
264 pos = self.previewView.scrollPosition() |
275 pos = self.previewView.scrollPosition() |
265 except AttributeError: |
276 except AttributeError: |
266 pos = self.__execJavaScript( |
277 pos = self.__execJavaScript( |
267 "(function() {" |
278 "(function() {" |
283 """ |
294 """ |
284 Private method to restore scroll bar positions for a previewed editor. |
295 Private method to restore scroll bar positions for a previewed editor. |
285 """ |
296 """ |
286 if self.__previewedPath not in self.__scrollBarPositions: |
297 if self.__previewedPath not in self.__scrollBarPositions: |
287 return |
298 return |
288 |
299 |
289 pos = self.__scrollBarPositions[self.__previewedPath] |
300 pos = self.__scrollBarPositions[self.__previewedPath] |
290 self.previewView.page().runJavaScript( |
301 self.previewView.page().runJavaScript( |
291 "window.scrollTo({0}, {1});".format(pos.x(), pos.y())) |
302 "window.scrollTo({0}, {1});".format(pos.x(), pos.y()) |
292 |
303 ) |
|
304 |
293 def __execJavaScript(self, script): |
305 def __execJavaScript(self, script): |
294 """ |
306 """ |
295 Private function to execute a JavaScript function Synchroneously. |
307 Private function to execute a JavaScript function Synchroneously. |
296 |
308 |
297 @param script JavaScript script source to be executed |
309 @param script JavaScript script source to be executed |
298 @type str |
310 @type str |
299 @return result of the script |
311 @return result of the script |
300 @rtype depending upon script result |
312 @rtype depending upon script result |
301 """ |
313 """ |
302 from PyQt6.QtCore import QEventLoop |
314 from PyQt6.QtCore import QEventLoop |
|
315 |
303 loop = QEventLoop() |
316 loop = QEventLoop() |
304 resultDict = {"res": None} |
317 resultDict = {"res": None} |
305 |
318 |
306 def resultCallback(res, resDict=resultDict): |
319 def resultCallback(res, resDict=resultDict): |
307 if loop and loop.isRunning(): |
320 if loop and loop.isRunning(): |
308 resDict["res"] = res |
321 resDict["res"] = res |
309 loop.quit() |
322 loop.quit() |
310 |
323 |
311 self.previewView.page().runJavaScript( |
324 self.previewView.page().runJavaScript(script, resultCallback) |
312 script, resultCallback) |
325 |
313 |
|
314 loop.exec() |
326 loop.exec() |
315 return resultDict["res"] |
327 return resultDict["res"] |
316 |
328 |
317 |
329 |
318 class PreviewProcessingThread(QThread): |
330 class PreviewProcessingThread(QThread): |
319 """ |
331 """ |
320 Class implementing a thread to process some text into HTML usable by the |
332 Class implementing a thread to process some text into HTML usable by the |
321 previewer view. |
333 previewer view. |
322 |
334 |
323 @signal htmlReady(str, str, str) emitted with the file name, the processed |
335 @signal htmlReady(str, str, str) emitted with the file name, the processed |
324 HTML and the web site root path to signal the availability of the |
336 HTML and the web site root path to signal the availability of the |
325 processed HTML |
337 processed HTML |
326 """ |
338 """ |
|
339 |
327 htmlReady = pyqtSignal(str, str, str) |
340 htmlReady = pyqtSignal(str, str, str) |
328 |
341 |
329 def __init__(self, parent=None): |
342 def __init__(self, parent=None): |
330 """ |
343 """ |
331 Constructor |
344 Constructor |
332 |
345 |
333 @param parent reference to the parent object (QObject) |
346 @param parent reference to the parent object (QObject) |
334 """ |
347 """ |
335 super().__init__() |
348 super().__init__() |
336 |
349 |
337 self.__lock = threading.Lock() |
350 self.__lock = threading.Lock() |
338 |
351 |
339 def process(self, filePath, language, text, ssiEnabled, rootPath, |
352 def process( |
340 useSphinx, convertNewLineToBreak, usePyMdownExtensions, |
353 self, |
341 markdownHtmlFormat, restDocutilsHtmlFormat): |
354 filePath, |
|
355 language, |
|
356 text, |
|
357 ssiEnabled, |
|
358 rootPath, |
|
359 useSphinx, |
|
360 convertNewLineToBreak, |
|
361 usePyMdownExtensions, |
|
362 markdownHtmlFormat, |
|
363 restDocutilsHtmlFormat, |
|
364 ): |
342 """ |
365 """ |
343 Public method to convert the given text to HTML. |
366 Public method to convert the given text to HTML. |
344 |
367 |
345 @param filePath file path of the text |
368 @param filePath file path of the text |
346 @type str |
369 @type str |
347 @param language language of the text |
370 @param language language of the text |
348 @type str |
371 @type str |
349 @param text text to be processed |
372 @param text text to be processed |
554 """ |
600 """ |
555 if useSphinx: |
601 if useSphinx: |
556 return self.__convertReSTSphinx(text) |
602 return self.__convertReSTSphinx(text) |
557 else: |
603 else: |
558 return self.__convertReSTDocutils(text, restDocutilsHtmlFormat) |
604 return self.__convertReSTDocutils(text, restDocutilsHtmlFormat) |
559 |
605 |
560 def __convertReSTSphinx(self, text): |
606 def __convertReSTSphinx(self, text): |
561 """ |
607 """ |
562 Private method to convert ReST text into HTML using 'sphinx'. |
608 Private method to convert ReST text into HTML using 'sphinx'. |
563 |
609 |
564 @param text text to be processed (string) |
610 @param text text to be processed (string) |
565 @return processed HTML (string) |
611 @return processed HTML (string) |
566 """ |
612 """ |
567 try: |
613 try: |
568 from sphinx.application import Sphinx # __IGNORE_EXCEPTION__ |
614 from sphinx.application import Sphinx # __IGNORE_EXCEPTION__ |
569 except ImportError: |
615 except ImportError: |
570 return self.tr( |
616 return self.tr( |
571 """<p>ReStructuredText preview requires the""" |
617 """<p>ReStructuredText preview requires the""" |
572 """ <b>sphinx</b> package.<br/>Install it with""" |
618 """ <b>sphinx</b> package.<br/>Install it with""" |
573 """ your package manager,'pip install Sphinx' or see""" |
619 """ your package manager,'pip install Sphinx' or see""" |
574 """ <a href="http://pypi.python.org/pypi/Sphinx">""" |
620 """ <a href="http://pypi.python.org/pypi/Sphinx">""" |
575 """this page.</a></p>""" |
621 """this page.</a></p>""" |
576 """<p>Alternatively you may disable Sphinx usage""" |
622 """<p>Alternatively you may disable Sphinx usage""" |
577 """ on the Editor, Filehandling configuration page.</p>""") |
623 """ on the Editor, Filehandling configuration page.</p>""" |
578 |
624 ) |
|
625 |
579 srcTempDir = tempfile.mkdtemp(prefix="eric-rest-src-") |
626 srcTempDir = tempfile.mkdtemp(prefix="eric-rest-src-") |
580 outTempDir = tempfile.mkdtemp(prefix="eric-rest-out-") |
627 outTempDir = tempfile.mkdtemp(prefix="eric-rest-out-") |
581 doctreeTempDir = tempfile.mkdtemp(prefix="eric-rest-doctree-") |
628 doctreeTempDir = tempfile.mkdtemp(prefix="eric-rest-doctree-") |
582 try: |
629 try: |
583 filename = 'sphinx_preview' |
630 filename = "sphinx_preview" |
584 basePath = os.path.join(srcTempDir, filename) |
631 basePath = os.path.join(srcTempDir, filename) |
585 with open(basePath + '.rst', 'w', encoding='utf-8') as fh: |
632 with open(basePath + ".rst", "w", encoding="utf-8") as fh: |
586 fh.write(text) |
633 fh.write(text) |
587 |
634 |
588 overrides = {'html_add_permalinks': False, |
635 overrides = { |
589 'html_copy_source': False, |
636 "html_add_permalinks": False, |
590 'html_title': 'Sphinx preview', |
637 "html_copy_source": False, |
591 'html_use_index': False, |
638 "html_title": "Sphinx preview", |
592 'html_use_modindex': False, |
639 "html_use_index": False, |
593 'html_use_smartypants': True, |
640 "html_use_modindex": False, |
594 'master_doc': filename} |
641 "html_use_smartypants": True, |
595 app = Sphinx(srcdir=srcTempDir, confdir=None, outdir=outTempDir, |
642 "master_doc": filename, |
596 doctreedir=doctreeTempDir, buildername='html', |
643 } |
597 confoverrides=overrides, status=None, |
644 app = Sphinx( |
598 warning=io.StringIO()) |
645 srcdir=srcTempDir, |
|
646 confdir=None, |
|
647 outdir=outTempDir, |
|
648 doctreedir=doctreeTempDir, |
|
649 buildername="html", |
|
650 confoverrides=overrides, |
|
651 status=None, |
|
652 warning=io.StringIO(), |
|
653 ) |
599 app.build(force_all=True, filenames=None) |
654 app.build(force_all=True, filenames=None) |
600 |
655 |
601 basePath = os.path.join(outTempDir, filename) |
656 basePath = os.path.join(outTempDir, filename) |
602 with open(basePath + '.html', 'r', encoding='utf-8') as fh: |
657 with open(basePath + ".html", "r", encoding="utf-8") as fh: |
603 html = fh.read() |
658 html = fh.read() |
604 finally: |
659 finally: |
605 shutil.rmtree(srcTempDir) |
660 shutil.rmtree(srcTempDir) |
606 shutil.rmtree(outTempDir) |
661 shutil.rmtree(outTempDir) |
607 shutil.rmtree(doctreeTempDir) |
662 shutil.rmtree(doctreeTempDir) |
608 |
663 |
609 return html |
664 return html |
610 |
665 |
611 def __convertReSTDocutils(self, text, htmlFormat): |
666 def __convertReSTDocutils(self, text, htmlFormat): |
612 """ |
667 """ |
613 Private method to convert ReST text into HTML using 'docutils'. |
668 Private method to convert ReST text into HTML using 'docutils'. |
614 |
669 |
615 @param text text to be processed (string) |
670 @param text text to be processed (string) |
616 @param htmlFormat HTML format to be generated (string) |
671 @param htmlFormat HTML format to be generated (string) |
617 @return processed HTML (string) |
672 @return processed HTML (string) |
618 """ |
673 """ |
619 if 'sphinx' in sys.modules: |
674 if "sphinx" in sys.modules: |
620 # Make sure any Sphinx polution of docutils has been removed. |
675 # Make sure any Sphinx polution of docutils has been removed. |
621 unloadKeys = [k for k in sys.modules.keys() |
676 unloadKeys = [ |
622 if k.startswith(('docutils', 'sphinx'))] |
677 k for k in sys.modules.keys() if k.startswith(("docutils", "sphinx")) |
|
678 ] |
623 for key in unloadKeys: |
679 for key in unloadKeys: |
624 sys.modules.pop(key) |
680 sys.modules.pop(key) |
625 |
681 |
626 try: |
682 try: |
627 import docutils.core # __IGNORE_EXCEPTION__ |
683 import docutils.core # __IGNORE_EXCEPTION__ |
628 import docutils.utils # __IGNORE_EXCEPTION__ |
684 import docutils.utils # __IGNORE_EXCEPTION__ |
629 except ImportError: |
685 except ImportError: |
630 return self.tr( |
686 return self.tr( |
631 """<p>ReStructuredText preview requires the""" |
687 """<p>ReStructuredText preview requires the""" |
632 """ <b>python-docutils</b> package.<br/>Install it with""" |
688 """ <b>python-docutils</b> package.<br/>Install it with""" |
633 """ your package manager, 'pip install docutils' or see""" |
689 """ your package manager, 'pip install docutils' or see""" |
634 """ <a href="http://pypi.python.org/pypi/docutils">""" |
690 """ <a href="http://pypi.python.org/pypi/docutils">""" |
635 """this page.</a></p>""") |
691 """this page.</a></p>""" |
636 |
692 ) |
|
693 |
637 # redirect sys.stderr because we are not interested in it here |
694 # redirect sys.stderr because we are not interested in it here |
638 origStderr = sys.stderr |
695 origStderr = sys.stderr |
639 sys.stderr = io.StringIO() |
696 sys.stderr = io.StringIO() |
640 try: |
697 try: |
641 html = docutils.core.publish_string( |
698 html = docutils.core.publish_string( |
642 text, writer_name=htmlFormat.lower()).decode("utf-8") |
699 text, writer_name=htmlFormat.lower() |
|
700 ).decode("utf-8") |
643 except docutils.utils.SystemMessage as err: |
701 except docutils.utils.SystemMessage as err: |
644 errStr = str(err).split(":")[-1].replace("\n", "<br/>") |
702 errStr = str(err).split(":")[-1].replace("\n", "<br/>") |
645 return self.tr( |
703 return self.tr("""<p>Docutils returned an error:</p><p>{0}</p>""").format( |
646 """<p>Docutils returned an error:</p><p>{0}</p>""" |
704 errStr |
647 ).format(errStr) |
705 ) |
648 |
706 |
649 sys.stderr = origStderr |
707 sys.stderr = origStderr |
650 return html |
708 return html |
651 |
709 |
652 def __convertMarkdown(self, text, convertNewLineToBreak, |
710 def __convertMarkdown( |
653 usePyMdownExtensions, htmlFormat): |
711 self, text, convertNewLineToBreak, usePyMdownExtensions, htmlFormat |
|
712 ): |
654 """ |
713 """ |
655 Private method to convert Markdown text into HTML. |
714 Private method to convert Markdown text into HTML. |
656 |
715 |
657 @param text text to be processed |
716 @param text text to be processed |
658 @type str |
717 @type str |
659 @param convertNewLineToBreak flag indicating to convert new lines |
718 @param convertNewLineToBreak flag indicating to convert new lines |
660 to HTML break (Markdown only) |
719 to HTML break (Markdown only) |
661 @type bool |
720 @type bool |
666 @type str |
725 @type str |
667 @return processed HTML |
726 @return processed HTML |
668 @rtype str |
727 @rtype str |
669 """ |
728 """ |
670 try: |
729 try: |
671 import markdown # __IGNORE_EXCEPTION__ |
730 import markdown # __IGNORE_EXCEPTION__ |
672 except ImportError: |
731 except ImportError: |
673 return self.tr( |
732 return self.tr( |
674 """<p>Markdown preview requires the <b>Markdown</b> """ |
733 """<p>Markdown preview requires the <b>Markdown</b> """ |
675 """package.<br/>Install it with your package manager,""" |
734 """package.<br/>Install it with your package manager,""" |
676 """ 'pip install Markdown' or see """ |
735 """ 'pip install Markdown' or see """ |
677 """<a href="http://pythonhosted.org/Markdown/install.html">""" |
736 """<a href="http://pythonhosted.org/Markdown/install.html">""" |
678 """installation instructions.</a></p>""") |
737 """installation instructions.</a></p>""" |
679 |
738 ) |
|
739 |
680 from . import PreviewerHTMLStyles |
740 from . import PreviewerHTMLStyles |
681 from . import MarkdownExtensions |
741 from . import MarkdownExtensions |
682 |
742 |
683 extensions = [] |
743 extensions = [] |
684 |
744 |
685 mermaidNeeded = False |
745 mermaidNeeded = False |
686 if ( |
746 if Preferences.getEditor( |
687 Preferences.getEditor("PreviewMarkdownMermaid") and |
747 "PreviewMarkdownMermaid" |
688 MarkdownExtensions.MermaidRegexFullText.search(text) |
748 ) and MarkdownExtensions.MermaidRegexFullText.search(text): |
689 ): |
|
690 extensions.append(MarkdownExtensions.MermaidExtension()) |
749 extensions.append(MarkdownExtensions.MermaidExtension()) |
691 mermaidNeeded = True |
750 mermaidNeeded = True |
692 |
751 |
693 if convertNewLineToBreak: |
752 if convertNewLineToBreak: |
694 extensions.append('nl2br') |
753 extensions.append("nl2br") |
695 |
754 |
696 pyMdown = False |
755 pyMdown = False |
697 if usePyMdownExtensions: |
756 if usePyMdownExtensions: |
698 with contextlib.suppress(ImportError): |
757 with contextlib.suppress(ImportError): |
699 import pymdownx # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ |
758 import pymdownx # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ |
|
759 |
700 # PyPI package is 'pymdown-extensions' |
760 # PyPI package is 'pymdown-extensions' |
701 |
761 |
702 extensions.extend([ |
762 extensions.extend( |
703 'toc', |
763 [ |
704 'pymdownx.extra', 'pymdownx.caret', 'pymdownx.emoji', |
764 "toc", |
705 'pymdownx.mark', 'pymdownx.tilde', 'pymdownx.keys', |
765 "pymdownx.extra", |
706 'pymdownx.tasklist', 'pymdownx.smartsymbols', |
766 "pymdownx.caret", |
707 ]) |
767 "pymdownx.emoji", |
|
768 "pymdownx.mark", |
|
769 "pymdownx.tilde", |
|
770 "pymdownx.keys", |
|
771 "pymdownx.tasklist", |
|
772 "pymdownx.smartsymbols", |
|
773 ] |
|
774 ) |
708 pyMdown = True |
775 pyMdown = True |
709 |
776 |
710 if not pyMdown: |
777 if not pyMdown: |
711 extensions.extend(['extra', 'toc']) |
778 extensions.extend(["extra", "toc"]) |
712 |
779 |
713 # version 2.0 supports only extension names, not instances |
780 # version 2.0 supports only extension names, not instances |
714 if ( |
781 if markdown.version_info[0] > 2 or ( |
715 markdown.version_info[0] > 2 or |
782 markdown.version_info[0] == 2 and markdown.version_info[1] > 0 |
716 (markdown.version_info[0] == 2 and |
|
717 markdown.version_info[1] > 0) |
|
718 ): |
783 ): |
719 extensions.append(MarkdownExtensions.SimplePatternExtension()) |
784 extensions.append(MarkdownExtensions.SimplePatternExtension()) |
720 |
785 |
721 if Preferences.getEditor("PreviewMarkdownMathJax"): |
786 if Preferences.getEditor("PreviewMarkdownMathJax"): |
722 mathjax = ( |
787 mathjax = ( |