--- a/eric6/UI/Previewers/PreviewerHTML.py Thu Oct 03 11:12:50 2019 +0200 +++ b/eric6/UI/Previewers/PreviewerHTML.py Fri Nov 01 16:11:27 2019 +0100 @@ -211,6 +211,8 @@ self.ssiCheckBox.isChecked(), rootPath, Preferences.getEditor("PreviewRestUseSphinx"), Preferences.getEditor("PreviewMarkdownNLtoBR"), + Preferences.getEditor( + "PreviewMarkdownUsePyMdownExtensions"), Preferences.getEditor("PreviewMarkdownHTMLFormat"), Preferences.getEditor("PreviewRestDocutilsHTMLFormat")) @@ -338,25 +340,34 @@ self.__lock = threading.Lock() def process(self, filePath, language, text, ssiEnabled, rootPath, - useSphinx, convertNewLineToBreak, markdownHtmlFormat, - restDocutilsHtmlFormat): + useSphinx, convertNewLineToBreak, usePyMdownExtensions, + markdownHtmlFormat, restDocutilsHtmlFormat): """ Public method to convert the given text to HTML. - @param filePath file path of the text (string) - @param language language of the text (string) - @param text text to be processed (string) + @param filePath file path of the text + @type str + @param language language of the text + @type str + @param text text to be processed + @type str @param ssiEnabled flag indicating to do some (limited) SSI processing - (boolean) - @param rootPath root path to be used for SSI processing (str) + @type bool + @param rootPath root path to be used for SSI processing + @type str @param useSphinx flag indicating to use Sphinx to generate the - ReST preview (boolean) + ReST preview + @type bool @param convertNewLineToBreak flag indicating to convert new lines - to HTML break (Markdown only) (boolean) + to HTML break (Markdown only) + @type bool + @param usePyMdownExtensions flag indicating to enable the PyMdown + extensions, if they are available + @type bool @param markdownHtmlFormat HTML format to be generated by markdown - (string) + @type str @param restDocutilsHtmlFormat HTML format to be generated by docutils - (string) + @type str """ with self.__lock: self.__filePath = filePath @@ -367,6 +378,7 @@ self.__haveData = True self.__useSphinx = useSphinx self.__convertNewLineToBreak = convertNewLineToBreak + self.__usePyMdownExtensions = usePyMdownExtensions self.__markdownHtmlFormat = markdownHtmlFormat self.__restDocutilsHtmlFormat = restDocutilsHtmlFormat if not self.isRunning(): @@ -386,6 +398,7 @@ rootPath = self.__rootPath useSphinx = self.__useSphinx convertNewLineToBreak = self.__convertNewLineToBreak + usePyMdownExtensions = self.__usePyMdownExtensions markdownHtmlFormat = self.__markdownHtmlFormat restDocutilsHtmlFormat = self.__restDocutilsHtmlFormat @@ -393,7 +406,8 @@ html = self.__getHtml(language, text, ssiEnabled, filePath, rootPath, useSphinx, convertNewLineToBreak, - markdownHtmlFormat, restDocutilsHtmlFormat) + usePyMdownExtensions, markdownHtmlFormat, + restDocutilsHtmlFormat) with self.__lock: if not self.__haveData: @@ -402,27 +416,37 @@ # else - next iteration def __getHtml(self, language, text, ssiEnabled, filePath, rootPath, - useSphinx, convertNewLineToBreak, markdownHtmlFormat, - restDocutilsHtmlFormat): + useSphinx, convertNewLineToBreak, usePyMdownExtensions, + markdownHtmlFormat, restDocutilsHtmlFormat): """ Private method to process the given text depending upon the given language. - @param language language of the text (string) - @param text to be processed (string) + @param language language of the text + @type str + @param text to be processed + @type str @param ssiEnabled flag indicating to do some (limited) SSI processing - (boolean) - @param filePath file path of the text (string) - @param rootPath root path to be used for SSI processing (str) + @type bool + @param filePath file path of the text + @type str + @param rootPath root path to be used for SSI processing + @type str @param useSphinx flag indicating to use Sphinx to generate the - ReST preview (boolean) + ReST preview + @type bool @param convertNewLineToBreak flag indicating to convert new lines - to HTML break (Markdown only) (boolean) + to HTML break (Markdown only) + @type bool + @param usePyMdownExtensions flag indicating to enable the PyMdown + extensions, if they are available + @type bool @param markdownHtmlFormat HTML format to be generated by markdown - (string) + @type str @param restDocutilsHtmlFormat HTML format to be generated by docutils - (string) - @return processed HTML text (string) + @type str + @return processed HTML text + @rtype str """ if language == "HTML": if ssiEnabled: @@ -431,8 +455,9 @@ html = text return self.__processRootPath(html, rootPath) elif language == "Markdown": - return self.__convertMarkdown(text, convertNewLineToBreak, - markdownHtmlFormat) + return self.__convertMarkdown( + text, convertNewLineToBreak, usePyMdownExtensions, + markdownHtmlFormat) elif language == "ReST": return self.__convertReST(text, useSphinx, restDocutilsHtmlFormat) else: @@ -625,15 +650,23 @@ sys.stderr = origStderr return html - def __convertMarkdown(self, text, convertNewLineToBreak, htmlFormat): + def __convertMarkdown(self, text, convertNewLineToBreak, + usePyMdownExtensions, htmlFormat): """ Private method to convert Markdown text into HTML. - @param text text to be processed (string) + @param text text to be processed + @type str @param convertNewLineToBreak flag indicating to convert new lines - to HTML break (Markdown only) (boolean) - @param htmlFormat HTML format to be generated by markdown (string) - @return processed HTML (string) + to HTML break (Markdown only) + @type bool + @param usePyMdownExtensions flag indicating to enable the PyMdown + extensions, if they are available + @type bool + @param htmlFormat HTML format to be generated by markdown + @type str + @return processed HTML + @rtype str """ try: import markdown # __IGNORE_EXCEPTION__ @@ -645,49 +678,111 @@ """<a href="http://pythonhosted.org/Markdown/install.html">""" """installation instructions.</a></p>""") - try: - import mdx_mathjax # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ - except ImportError: - # mathjax doesn't require import statement if installed - # as extension - pass + from . import PreviewerHTMLStyles + from . import MarkdownExtensions + + extensions = [] + + mermaidNeeded = False + if Preferences.getEditor("PreviewMarkdownMermaid"): + if MarkdownExtensions.MermaidRegexFullText.search(text): + extensions.append(MarkdownExtensions.MermaidExtension()) + mermaidNeeded = True if convertNewLineToBreak: - extensions = ['fenced_code', 'nl2br', 'extra'] - else: - extensions = ['fenced_code', 'extra'] + extensions.append('nl2br') - # version 2.0 supports only extension names, not instances - if ( - markdown.version_info[0] > 2 or - (markdown.version_info[0] == 2 and - markdown.version_info[1] > 0) - ): - class _StrikeThroughExtension(markdown.Extension): - """ - Class is placed here, because it depends on imported markdown, - and markdown import is lazy. + pyMdown = False + if usePyMdownExtensions: + try: + import pymdownx # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ + # PyPI package is 'pymdown-extensions' - (see https://pythonhosted.org/Markdown/extensions/api.html - this page for details) - """ - DEL_RE = r'(~~)(.*?)~~' - - def extendMarkdown(self, md, md_globals): - # Create the del pattern - del_tag = markdown.inlinepatterns.SimpleTagPattern( - self.DEL_RE, 'del') - # Insert del pattern into markdown parser - md.inlinePatterns.add('del', del_tag, '>not_strong') + extensions.extend([ + 'toc', + 'pymdownx.extra', 'pymdownx.caret', 'pymdownx.emoji', + 'pymdownx.mark', 'pymdownx.tilde', 'pymdownx.keys', + 'pymdownx.tasklist', 'pymdownx.smartsymbols', + ]) + pyMdown = True + except ImportError: + pass + + if not pyMdown: + extensions.extend(['extra', 'toc']) - extensions.append(_StrikeThroughExtension()) + # version 2.0 supports only extension names, not instances + if ( + markdown.version_info[0] > 2 or + (markdown.version_info[0] == 2 and + markdown.version_info[1] > 0) + ): + extensions.append(MarkdownExtensions.SimplePatternExtension()) - try: - return markdown.markdown(text, extensions=extensions + ['mathjax'], - output_format=htmlFormat.lower()) - except (ImportError, ValueError): - # markdown raises ValueError or ImportError, depends on version - # It is not clear, how to distinguish missing mathjax from other - # errors. So keep going without mathjax. - return markdown.markdown(text, extensions=extensions, - output_format=htmlFormat.lower()) + if Preferences.getEditor("PreviewMarkdownMathJax"): + mathjax = ( + "<script type='text/javascript' id='MathJax-script' async" + " src='https://cdn.jsdelivr.net/npm/mathjax@3/es5/" + "tex-chtml.js'>\n" + "</script>\n" + ) + # prepare text for mathjax + text = ( + text + .replace(r"\(", r"\\(") + .replace(r"\)", r"\\)") + .replace(r"\[", r"\\[") + .replace(r"\]", r"\\]") + ) + else: + mathjax = "" + + if mermaidNeeded: + mermaid = ( + "<script type='text/javascript' id='Mermaid-script'" + " src='https://unpkg.com/mermaid@8/dist/mermaid.min.js'>\n" + "</script>\n" + ) + else: + mermaid = "" + + htmlFormat = Preferences.getEditor("PreviewMarkdownHTMLFormat").lower() + body = markdown.markdown(text, extensions=extensions, + output_format=htmlFormat.lower()) + + if htmlFormat == "xhtml1": + head = ( + '''<!DOCTYPE html PUBLIC "-//W3C//DTD''' + ''' XHTML 1.0 Transitional//EN"\n''' + ''' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional''' + '''.dtd">\n''' + '''<html xmlns="http://www.w3.org/1999/xhtml">\n''' + ) + elif htmlFormat == "html5": + head = ( + '''<!DOCTYPE html>\n''' + '''<html lang="EN">\n''' + ) + else: + head = '<html lang="EN">\n' + head += '''<head>\n''' + head += ( + '''<meta name="Generator" content="eric6" />\n''' + '''<meta http-equiv="Content-Type" ''' + '''content="text/html; charset=utf-8" />\n''' + '''{0}''' + '''{1}''' + '''<style type="text/css">''' + '''{2}''' + '''</style>\n''' + '''</head>\n''' + '''<body>\n''' + ).format( + mathjax, mermaid, + PreviewerHTMLStyles.css_markdown + + PreviewerHTMLStyles.css_pygments + ) + + foot = '''\n</body>\n</html>\n''' + + return head + body + foot