diff --git a/composer.json b/composer.json index 45ae7b8..24c5592 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "symfony/polyfill-intl-icu": "^1.10", "symfony/process": "^4.0", "webuni/commonmark-table-extension": "0.9.*", - "webuni/front-matter": "^1.0.0" + "webuni/front-matter": "^1.0.0", + "scrivo/highlight.php": "^9.15" }, "suggest":{ "ext-intl": "Allows to translate the modified at date" diff --git a/composer.lock b/composer.lock index 22b08ed..dffd4ef 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c2a8f08a3622dfcd70136f48e4f5a0ce", + "content-hash": "3df8e368fea890f0e123788f898cddc9", "packages": [ { "name": "guzzlehttp/guzzle", @@ -244,9 +244,9 @@ "authors": [ { "name": "Colin O'Dell", - "role": "Lead Developer", "email": "colinodell@gmail.com", - "homepage": "https://www.colinodell.com" + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" } ], "description": "PHP Markdown parser based on the CommonMark spec", @@ -500,6 +500,73 @@ "description": "A polyfill for getallheaders.", "time": "2016-02-11T07:05:27+00:00" }, + { + "name": "scrivo/highlight.php", + "version": "v9.15.10.0", + "source": { + "type": "git", + "url": "https://github.com/scrivo/highlight.php.git", + "reference": "9ad3adb4456dc91196327498dbbce6aa1ba1239e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scrivo/highlight.php/zipball/9ad3adb4456dc91196327498dbbce6aa1ba1239e", + "reference": "9ad3adb4456dc91196327498dbbce6aa1ba1239e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7", + "symfony/finder": "^2.8" + }, + "suggest": { + "ext-dom": "Needed to make use of the features in the utilities namespace" + }, + "type": "library", + "autoload": { + "psr-0": { + "Highlight\\": "", + "HighlightUtilities\\": "" + }, + "files": [ + "HighlightUtilities/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Geert Bergman", + "role": "Project Author", + "homepage": "http://www.scrivo.org/" + }, + { + "name": "Vladimir Jimenez", + "role": "Contributor", + "homepage": "https://allejo.io" + }, + { + "name": "Martin Folkers", + "role": "Contributor", + "homepage": "https://twobrain.io" + } + ], + "description": "Server side syntax highlighter that supports 185 languages. It's a PHP port of highlight.js", + "keywords": [ + "code", + "highlight", + "highlight.js", + "highlight.php", + "syntax" + ], + "time": "2019-08-27T04:27:48+00:00" + }, { "name": "symfony/console", "version": "v4.3.4", @@ -1503,8 +1570,8 @@ "authors": [ { "name": "Frank Kleine", - "homepage": "http://frankkleine.de/", - "role": "Developer" + "role": "Developer", + "homepage": "http://frankkleine.de/" } ], "description": "Virtual file system to mock the real file system in unit tests.", @@ -2018,8 +2085,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "role": "lead", + "email": "sebastian@phpunit.de" } ], "description": "Utility class for timing", diff --git a/docs/02_Examples/05_Code_Highlighting.md b/docs/02_Examples/05_Code_Highlighting.md index e723d73..0d0807b 100644 --- a/docs/02_Examples/05_Code_Highlighting.md +++ b/docs/02_Examples/05_Code_Highlighting.md @@ -2,21 +2,27 @@ Highlight.js highlights syntax in code examples on blogs, forums and in fact on You can even use [Github Flavored Markdown](!Features/CommonMark_compliant) +We also use the [scrivo/highlight.php](https://github.com/scrivo/highlight.php) package to highlight on rendering when possible. +Highlight.js is a powerful but heavy library, since we don't know which languages you'll use we included all of them. + +The good news is; if you use a fenced code block (` ``` `) with the language defined, the rendering is done on server side and entirely skips loading Highlight.js on the page. While still having the same end-result on your code. + **Python** - @requires_authorization - def somefunc(param1='', param2=0): - r'''A docstring''' - if param1 > param2: # interesting - print 'Gre\'ater' - return (param2 - param1 + 1) or None +```python +@requires_authorization +def somefunc(param1='', param2=0): + r'''A docstring''' + if param1 > param2: # interesting + print 'Gre\'ater' + return (param2 - param1 + 1) or None - class SomeClass:
pass - - >>> message = '''interpreter - ... prompt''' +class SomeClass:
pass +>>> message = '''interpreter +... prompt''' +``` **Python's profiler output** diff --git a/libs/Format/HTML/ContentTypes/Markdown/CommonMarkConverter.php b/libs/Format/HTML/ContentTypes/Markdown/CommonMarkConverter.php index 8fd5ea2..96d8f37 100644 --- a/libs/Format/HTML/ContentTypes/Markdown/CommonMarkConverter.php +++ b/libs/Format/HTML/ContentTypes/Markdown/CommonMarkConverter.php @@ -9,6 +9,8 @@ class CommonMarkConverter extends \Todaymade\Daux\ContentTypes\Markdown\CommonMa { parent::extendEnvironment($environment, $config); + $environment->addBlockRenderer('FencedCode', new FencedCodeRenderer()); + $environment->addDocumentProcessor(new TOC\Processor($config)); $environment->addBlockRenderer('Todaymade\Daux\ContentTypes\Markdown\TableOfContents', new TOC\Renderer($config)); } diff --git a/libs/Format/HTML/ContentTypes/Markdown/FencedCodeRenderer.php b/libs/Format/HTML/ContentTypes/Markdown/FencedCodeRenderer.php new file mode 100644 index 0000000..44f0d0a --- /dev/null +++ b/libs/Format/HTML/ContentTypes/Markdown/FencedCodeRenderer.php @@ -0,0 +1,70 @@ +hl = new Highlighter(); + } + + /** + * @param AbstractBlock $block + * @param HtmlRendererInterface $htmlRenderer + * @param bool $inTightList + * + * @return HtmlElement|string + */ + public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, $inTightList = false) + { + if (!($block instanceof FencedCode)) { + throw new \InvalidArgumentException('Incompatible block type: ' . get_class($block)); + } + + $attrs = []; + foreach ($block->getData('attributes', []) as $key => $value) { + $attrs[$key] = Xml::escape($value); + } + + $content = $block->getStringContent(); + + $language = $this->getLanguage($block->getInfoWords()); + $highlighted = false; + if ($language) { + $attrs['class'] = isset($attrs['class']) ? $attrs['class'] . ' ' : ''; + + try { + $highlighted = $this->hl->highlight($language, $content); + $content = $highlighted->value; + $attrs['class'] .= 'hljs ' . $highlighted->language; + } catch (Exception $e) { + $attrs['class'] .= 'language-' . $language; + } + } + + if (!$highlighted) { + $content = Xml::escape($content); + } + + return new HtmlElement( + 'pre', + [], + new HtmlElement('code', $attrs, $content) + ); + } + + public function getLanguage($infoWords) + { + if (count($infoWords) === 0 || strlen($infoWords[0]) === 0) { + return false; + } + + return Xml::escape($infoWords[0], true); + } +}