Skip to content

Commit

Permalink
Allow specifying a default language for code blocks (#2282)
Browse files Browse the repository at this point in the history
* Allow specifying a default language for code blocks

* Fix lint and spelling

* Spelling
  • Loading branch information
facelessuser authored Dec 30, 2023
1 parent fa30d26 commit 285aa16
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 35 deletions.
9 changes: 9 additions & 0 deletions docs/src/markdown/about/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 10.7

- **NEW**: Highlight: Added new option `default_lang` which will cause code blocks with no language specifier to be
highlighted with the specified default language instead of plain text. This affects indented code blocks and code
blocks defined with SuperFences.
- **NEW**: InlineHilite: `style_plain_text` can be specified with a language string (in addition to its previous
boolean requirement) to treat inline code blocks with no explicit language specifier with a specific default
language.

## 10.6

- **NEW**: MagicLink: Allow configuring custom repository providers based off the existing providers.
Expand Down
47 changes: 26 additions & 21 deletions docs/src/markdown/extensions/highlight.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,27 +130,28 @@ All options below control the Pygments' output. The three exceptions are `use_py
`css_class` which sets the name of the class assigned to the generated code blocks, and `code_attr_on_pre` which only
apply when Pygments is disabled. Many of these options are demonstrated in the [SuperFences](./superfences.md) documentation.

Option | Type | Default | Description
------------------------- | ------ | ----------------------| -----------
`css_class` | string | `#!py3 'highlight'` | Default class to apply to the wrapper element on code blocks. Other extensions can override this.
`guess_lang` | bool | `#!py3 False` | Guess what syntax language should be used if no language is specified. `#!py3 True` for always, `#!py3 False` for never, `#!py3 'block'` for block code only, and `#!py3 'inline'` for inline code only.
`pygments_style` | string | `#!py3 'default'` | Set the Pygments' style to use. This really only has an effect when used with `noclasses`.
`noclasses` | bool | `#!py3 False` | This will cause the styles to directly be written to the tag's style attribute instead of requiring a stylesheet.
`use_pygments` | bool | `#!py3 True` | Controls whether Pygments (if available) is used to style the code, or if the code will just be escaped and prepped for a JavaScript syntax highlighter.
`linenums` | bool | `#!py3 None` | Enable line numbers globally for *block* code. This will be ignored for *inline* code. If set to `#!py3 False` line numbers will be disabled globally and can not be turned on, not even per code block.
`linenums_special` | int | `#!py3 1` | Globally sets the specified nth lines' gutter with the class "special". This can be overridden in [SuperFences](./superfences.md) per fence if desired.
`linenums_style` | string | `#!py3 'table'` | Controls the output style when `linenums` are enabled. Supported styles are Pygments default `table` and `inline`, but also supported is the pymdown-extensions `pymdownx-inline` which provides a special inline mode, see [Line Number Styles](#line-number-styles) for more info.
`linenums_class` | string | `#!py3 'linenums'` | Controls the name of the line number class when Pygments is not used.
`extend_pygments_lang` | list | `#!py3 []` | A list of extended languages to add. See [Extended Pygments Lexer Options](#extended-pygments-lexer-options) for more info.
`language_prefix` | string | `#!py3 'language-'` | Controls the prefix applied to the language class when Pygments is not used. By default, uses the HTML5 prefix of `language-`.
`code_attr_on_pre` | bool | `#!py3 False` | By default, the language class and all attributes added via the [`attr_list`][attr-list] extension are attached to the `#!html <code>` element. This forces them to be attached to the `#!html <pre>` element.
`auto_title` | bool | `#!py3 False` | When using Pygments, for all code blocks generate a title header with the name of the lexer being used. The lexer name is pulled directly from Pygments and title cased appropriately.
`auto_title_map` | dict | `#!py3 {}` | A dictionary used to override certain titles returned by `auto_title`. Simply specify the title to override as the key and the desired title as the value.
`line_spans` | string | `#!py3 ''` | Controls the Pygments option of a similar name. If set to a nonempty string, e.g. `foo`, the formatter will wrap each output line in a `#!html <span>` tag with an id of `foo-<code_block_number>-<line_number>`.
`anchor_linenums` | bool | `#!py3 False` | Enables the Pygments option of a similar name. If set to `#!py True`, will wrap line numbers in `#!html <a>` tags. Used in combination with `linenums` and `line_anchors`. If `line_anchors` is not configured, `__codelineno` will be assumed as the ID prefix.
`line_anchors` | bool | `#!py3 False` | Controls the Pygments option of a similar name. If set to a nonempty string, e.g. `foo`, the formatter will insert an anchor tag with an id (and name) of `foo-<code_block_number>-<line_number>`.
`pygments_lang_class` | bool | `#!py3 False` | If set to True, the language name used will be included as a class attached to the element with the associated `language_prefix`.
`stripnl` | bool | `#!py3 True` | Strips leading and trailing newlines from code blocks. This is Pygments default behavior. Setting this to `#!py False` disables this and will retain leading and trailing newlines. This has no affect on inline code.
Option | Type | Default | Description
------------------------- | -------------- | ----------------------| -----------
`css_class` | string | `#!py3 'highlight'` | Default class to apply to the wrapper element on code blocks. Other extensions can override this.
`guess_lang` | bool \| string | `#!py3 False` | Guess what syntax language should be used if no language is specified. `#!py3 True` for always, `#!py3 False` for never, `#!py3 'block'` for block code only, and `#!py3 'inline'` for inline code only.
`default_lang` | string | `#!py3 ''` | The assumed highlight language of a code block when no language is set. If something other than plain text is desired for indented blocks or for fenced code blocks (via [SuperFences](./superfences.md)) that have no configured language, this can be set to a language other than plain text. InlineHilite requires you to specifically set its own option (`style_plain_text`) to control this for inline code blocks.
`pygments_style` | string | `#!py3 'default'` | Set the Pygments' style to use. This really only has an effect when used with `noclasses`.
`noclasses` | bool | `#!py3 False` | This will cause the styles to directly be written to the tag's style attribute instead of requiring a stylesheet.
`use_pygments` | bool | `#!py3 True` | Controls whether Pygments (if available) is used to style the code, or if the code will just be escaped and prepped for a JavaScript syntax highlighter.
`linenums` | bool | `#!py3 None` | Enable line numbers globally for *block* code. This will be ignored for *inline* code. If set to `#!py3 False` line numbers will be disabled globally and can not be turned on, not even per code block.
`linenums_special` | int | `#!py3 1` | Globally sets the specified nth lines' gutter with the class "special". This can be overridden in [SuperFences](./superfences.md) per fence if desired.
`linenums_style` | string | `#!py3 'table'` | Controls the output style when `linenums` are enabled. Supported styles are Pygments default `table` and `inline`, but also supported is the pymdown-extensions `pymdownx-inline` which provides a special inline mode, see [Line Number Styles](#line-number-styles) for more info.
`linenums_class` | string | `#!py3 'linenums'` | Controls the name of the line number class when Pygments is not used.
`extend_pygments_lang` | list | `#!py3 []` | A list of extended languages to add. See [Extended Pygments Lexer Options](#extended-pygments-lexer-options) for more info.
`language_prefix` | string | `#!py3 'language-'` | Controls the prefix applied to the language class when Pygments is not used. By default, uses the HTML5 prefix of `language-`.
`code_attr_on_pre` | bool | `#!py3 False` | By default, the language class and all attributes added via the [`attr_list`][attr-list] extension are attached to the `#!html <code>` element. This forces them to be attached to the `#!html <pre>` element.
`auto_title` | bool | `#!py3 False` | When using Pygments, for all code blocks generate a title header with the name of the lexer being used. The lexer name is pulled directly from Pygments and title cased appropriately.
`auto_title_map` | dict | `#!py3 {}` | A dictionary used to override certain titles returned by `auto_title`. Simply specify the title to override as the key and the desired title as the value.
`line_spans` | string | `#!py3 ''` | Controls the Pygments option of a similar name. If set to a nonempty string, e.g. `foo`, the formatter will wrap each output line in a `#!html <span>` tag with an id of `foo-<code_block_number>-<line_number>`.
`anchor_linenums` | bool | `#!py3 False` | Enables the Pygments option of a similar name. If set to `#!py True`, will wrap line numbers in `#!html <a>` tags. Used in combination with `linenums` and `line_anchors`. If `line_anchors` is not configured, `__codelineno` will be assumed as the ID prefix.
`line_anchors` | bool | `#!py3 False` | Controls the Pygments option of a similar name. If set to a nonempty string, e.g. `foo`, the formatter will insert an anchor tag with an id (and name) of `foo-<code_block_number>-<line_number>`.
`pygments_lang_class` | bool | `#!py3 False` | If set to True, the language name used will be included as a class attached to the element with the associated `language_prefix`.
`stripnl` | bool | `#!py3 True` | Strips leading and trailing newlines from code blocks. This is Pygments default behavior. Setting this to `#!py False` disables this and will retain leading and trailing newlines. This has no affect on inline code.

/// new | New 7.1
`linenums_class` was added in `7.1`.
Expand All @@ -165,3 +166,7 @@ still enables globally.
/// new | New 9.0
`auto_title`, `auto_title_map`, `line_spans`, `anchor_linenums`, and `line_anchors` were all added in `9.0`.
///

/// new | New 10.7
`default_lang` added in 10.7.
///
14 changes: 9 additions & 5 deletions docs/src/markdown/extensions/inlinehilite.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,12 @@ def format_fail(src, language, class_name, md):

## Options

Option | Type | Default | Description
------------------------- | ------------ | ------------- | -----------
`css_class` | string | `#!py3 ''` | Class name is applied to the wrapper element of the code. If configured, this setting will override the `css_class` option of Highlight. If nothing is configured here or via or Highlight, the class `highlight` will be used.
`style_plain_text` | bool | `#!py3 False` | When `guess_lang` is set to `#!py3 False`, InlineHilite will avoid applying classes to code blocks that do not explicitly set a language. If it is desired to have plain text styled like code, enable this to inject classes so that they can all be styled the same.
`custom_inline` | [dictionary] | `#!py3 []` | Custom inline code blocks.
Option | Type | Default | Description
------------------------- | --------------- | ------------- | -----------
`css_class` | string | `#!py3 ''` | Class name is applied to the wrapper element of the code. If configured, this setting will override the `css_class` option of Highlight. If nothing is configured here or via or Highlight, the class `highlight` will be used.
`style_plain_text` | bool \| string | `#!py3 False` | When `guess_lang` is set to `#!py3 False`, InlineHilite will avoid applying classes to code blocks that do not explicitly set a language. If it is desired to have plain text styled like code, enable this to inject classes so that they can all be styled the same. By default, plain text is assumed, but if this option is set to a language string, the specified language will be used for any inline code blocks that does not explicitly set a language.
`custom_inline` | [dictionary] | `#!py3 []` | Custom inline code blocks.

/// new | New 10.7
`style_plain_text` now allows specifying a default language to be assumed.
///
16 changes: 13 additions & 3 deletions pymdownx/highlight.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@
'False disables this and will retain leading and trailing newlines. This has no affect on inline code. '
'- Defaults: True'
],
'default_lang': [
'',
'The assumed highlight language of a code block when no language is set. - Default text'
],
'_enabled': [
True,
'Used internally to communicate if extension has been explicitly enabled - Default: False'
Expand Down Expand Up @@ -236,7 +240,8 @@ def __init__(
noclasses=False, extend_pygments_lang=None, linenums=None, linenums_special=-1,
linenums_style='table', linenums_class='linenums', language_prefix='language-',
code_attr_on_pre=False, auto_title=False, auto_title_map=None, line_spans='',
anchor_linenums=False, line_anchors='', pygments_lang_class=False, stripnl=True
anchor_linenums=False, line_anchors='', pygments_lang_class=False, stripnl=True,
default_lang=''
):
"""Initialize."""

Expand All @@ -256,6 +261,7 @@ def __init__(
self.anchor_linenums = anchor_linenums
self.pygments_lang_class = pygments_lang_class
self.stripnl = stripnl
self.default_lang = default_lang

if self.anchor_linenums and not self.line_anchors:
self.line_anchors = '__codelineno'
Expand Down Expand Up @@ -305,7 +311,7 @@ def get_lexer(self, src, language, inline, stripnl):
except Exception: # pragma: no cover
pass
if lexer is None:
lexer = get_lexer_by_name('text', **lexer_options)
lexer = get_lexer_by_name(self.default_lang or 'text', **lexer_options)
name = lexer.aliases[0]
return lexer, name

Expand Down Expand Up @@ -333,6 +339,9 @@ def highlight(
) and not inline > 0
class_str = ''

if not language and self.default_lang:
language = self.default_lang

# Convert with Pygments.
if pygments and self.use_pygments:

Expand Down Expand Up @@ -513,7 +522,8 @@ def run(self, root):
auto_title=self.config['auto_title'],
auto_title_map=self.config['auto_title_map'],
pygments_lang_class=self.config['pygments_lang_class'],
stripnl=self.config['stripnl']
stripnl=self.config['stripnl'],
default_lang=self.config['default_lang']
)
placeholder = self.md.htmlStash.store(
code.highlight(
Expand Down
13 changes: 8 additions & 5 deletions pymdownx/inlinehilite.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def highlight_code(self, src='', language='', classname=None, md=None):
"""Syntax highlight the inline code block."""

process_text = self.style_plain_text or language or self.guess_lang
default_lang = self.style_plain_text if isinstance(self.style_plain_text, str) else ''

if process_text:
el = self.highlighter(
Expand All @@ -146,7 +147,8 @@ def highlight_code(self, src='', language='', classname=None, md=None):
noclasses=self.noclasses,
extend_pygments_lang=self.extend_pygments_lang,
language_prefix=self.language_prefix,
pygments_lang_class=self.pygments_lang_class
pygments_lang_class=self.pygments_lang_class,
default_lang=default_lang
).highlight(src, language, self.css_class, inline=True)
el.text = self.md.htmlStash.store(el.text)
else:
Expand Down Expand Up @@ -194,11 +196,12 @@ def __init__(self, *args, **kwargs):
self.inlinehilite = []
self.config = {
'style_plain_text': [
False,
"Process inline code even when a language is not specified "
"or langauge is specified as 'text'. "
"When 'False', no classes will be added to 'text' code blocks"
0,
"Process inline code even when a language is not specified. "
"When 'False', no classes will be added to code blocks without shebangs "
"and no scoping will performed. The content will just be escaped."
"If a language string is provided, then that language will be assumed "
"for any inline code block without a shebang. "
"- Default: False"
],
'css_class': [
Expand Down
4 changes: 3 additions & 1 deletion pymdownx/superfences.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ def get_hl_settings(self):
self.anchor_linenums = config.get('anchor_linenums', False)
self.pygments_lang_class = config.get('pygments_lang_class', False)
self.stripnl = config.get('stripnl', True)
self.default_lang = config.get('default_lang', True)

def clear(self):
"""Reset the class variables."""
Expand Down Expand Up @@ -797,7 +798,8 @@ def highlight(self, src="", language="", options=None, md=None, **kwargs):
line_anchors=self.line_anchors,
anchor_linenums=self.anchor_linenums,
pygments_lang_class=self.pygments_lang_class,
stripnl=self.stripnl
stripnl=self.stripnl,
default_lang=self.default_lang
).highlight(
src,
language,
Expand Down
68 changes: 68 additions & 0 deletions tests/test_extensions/test_highlight.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,3 +660,71 @@ def test_extended_lang_case(self):
''', # noqa: E501
True
)


class TestDefaultLang(util.MdCase):
"""Test default block language cases."""

extension = ['pymdownx.highlight', 'pymdownx.superfences', 'pymdownx.inlinehilite']
extension_configs = {
'pymdownx.highlight': {
'default_lang': 'python'
}
}

def test_default_block(self):
"""Test that default language affects block, but not inline code."""

self.check_markdown(
'''
`import code`
import code
```
import code
```
''',
'''
<p><code>import code</code></p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">code</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">code</span>
</code></pre></div>
''',
True
)


class TestDefaultLangInline(util.MdCase):
"""Test default inline language cases."""

extension = ['pymdownx.highlight', 'pymdownx.superfences', 'pymdownx.inlinehilite']
extension_configs = {
'pymdownx.inlinehilite': {
'style_plain_text': 'python'
}
}

def test_default_inline(self):
"""Test that default language affects block, but not inline code."""

self.check_markdown(
'''
`import code`
import code
```
import code
```
''',
'''
<p><code class="highlight"><span class="kn">import</span> <span class="nn">code</span></code></p>
<div class="highlight"><pre><span></span><code>import code
</code></pre></div>
<div class="highlight"><pre><span></span><code>import code
</code></pre></div>
''',
True
)

0 comments on commit 285aa16

Please sign in to comment.