Skip to content

Commit

Permalink
Add support for *tag1 **tag1,tag2*** cases for *, _, ~, and `…
Browse files Browse the repository at this point in the history
…^` (#2467)

* Add support for `*em **em,strong***` cases for `*` and `_`

Fixes #2466

* Add support for additional single, double cases for tilde and caret

Clean up formatting as well

* Spelling

* Add more tests and move some tests for tilde to new test style

* More tests and move some caret tests

* Fix minor typo in BetterEm docs
  • Loading branch information
facelessuser authored Sep 26, 2024
1 parent 5f0e3fc commit 7a95865
Show file tree
Hide file tree
Showing 19 changed files with 649 additions and 187 deletions.
6 changes: 6 additions & 0 deletions docs/src/markdown/about/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 10.10.2

- **FIX**: BetterEm: Add better support for `*em, **em,strong***` and `_em, __em,strong___` cases.
- **FIX**: Caret: Add better support for `*sup, **sup,ins***`.
- **FIX**: Tilde: Add better support for `*sub, **sub,del***`.

## 10.10.1

- **FIX**: FancyLists: Remove a mistaken semicolon from injected classes.
Expand Down
5 changes: 2 additions & 3 deletions docs/src/markdown/extensions/betterem.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ ___A lot of underscores____________is okay___
___A lot of underscores____________is okay___
///

BetterEm will also ensure that smart mode breaks proper when an inner like token signifies an end.

BetterEm will also ensure that smart mode breaks properly when an inner like token signifies an end.

```text title="Smart Break"
__This will all be bold __because of the placement of the center underscores.__
Expand All @@ -94,7 +93,7 @@ __This will all be bold_ because of the token is less than that of the surroundi
/// html | div.result
__This will all be bold __because of the placement of the center underscores.__

__This will all be bold __ because of the placement of the center asterisks.__
__This will all be bold __ because of the placement of the center underscores.__

__This will NOT all be bold__ because of the placement of the center underscores.__

Expand Down
64 changes: 42 additions & 22 deletions pymdownx/betterem.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,55 +54,71 @@
# __strong_em,strong___
UNDER_STRONG_EM3 = r'(_{{2}})(?![\s_]){}_(?![\s_]){}(?<!\s)_{{3}}'.format(UNDER_CONTENT, UNDER_CONTENT)
# **strong**
STAR_STRONG = r'(\*{2})(?!\s)%s(?<!\s)\1' % STAR_CONTENT2
STAR_STRONG = r'(\*{{2}})(?!\s){}(?<!\s)\1'.format(STAR_CONTENT2)
# __strong__
UNDER_STRONG = r'(_{2})(?!\s)%s(?<!\s)\1' % UNDER_CONTENT2

UNDER_STRONG = r'(_{{2}})(?!\s){}(?<!\s)\1'.format(UNDER_CONTENT2)
# *em **strong***
STAR_EM_STRONG2 = r'(?<!\*)(\*)(?![\*\s]){}\*{{2}}{}\*{{3}}'.format(STAR_CONTENT, STAR_CONTENT2)
# _em __strong___
UNDER_EM_STRONG2 = r'(?<!_)(_)(?![_\s]){}_{{2}}{}_{{3}}'.format(UNDER_CONTENT, UNDER_CONTENT2)
# Prioritize *value* when **value** is nested within
STAR_EM2 = r'(?<!\*)(\*)(?![\*\s])(.+?)(?<![\*\s])(\*)(?!\*)'
# Prioritize _value_ when __value__ is nested within
UNDER_EM2 = r'(?<!_)(_)(?![_\s])(.+?)(?<![_\s])(_)(?!_)'

# *emphasis*
STAR_EM = r'(\*)(?!\s)%s(?<!\s)\1' % STAR_CONTENT
STAR_EM = r'(\*)(?!\s){}(?<!\s)\1'.format(STAR_CONTENT)
# _emphasis_
UNDER_EM = r'(_)(?!\s)%s(?<!\s)\1' % UNDER_CONTENT
UNDER_EM = r'(_)(?!\s){}(?<!\s)\1'.format(UNDER_CONTENT)

# Smart rules for when "smart underscore" is enabled
# SMART: ___strong,em___
SMART_UNDER_STRONG_EM = r'(?<!\w)(_{3})(?![\s_])%s(?<!\s)\1(?!\w)' % SMART_UNDER_CONTENT
# ___strong,em_ strong__
SMART_UNDER_STRONG_EM = r'(?<!\w)(_{{3}})(?![\s_]){}(?<!\s)\1(?!\w)'.format(SMART_UNDER_CONTENT)
# SMART: ___strong,em_ strong__
SMART_UNDER_STRONG_EM2 = \
r'(?<!\w)(_{{3}})(?![\s_]){}(?<!\s)_(?!\w){}(?<!\s)_{{2}}(?!\w)'.format(SMART_UNDER_CONTENT, SMART_UNDER_CONTENT)
# ___em,strong__ em_
# SMART: ___em,strong__ em_
SMART_UNDER_EM_STRONG = \
r'(?<!\w)(_{{3}})(?![\s_]){}(?<!\s)_{{2}}(?!\w){}(?<!\s)_(?!\w)'.format(SMART_UNDER_CONTENT, SMART_UNDER_CONTENT)
# __strong__
SMART_UNDER_STRONG = r'(?<!\w)(_{2})(?![\s_])%s(?<!\s)\1(?!\w)' % SMART_UNDER_CONTENT
# SMART _em_
SMART_UNDER_EM = r'(?<!\w)(_)(?![\s_])%s(?<!\s)\1(?!\w)' % SMART_UNDER_CONTENT
# Prioritize _value_ when __value__ is nested within
# SMART: __strong__
SMART_UNDER_STRONG = r'(?<!\w)(_{{2}})(?![\s_]){}(?<!\s)\1(?!\w)'.format(SMART_UNDER_CONTENT)
# SMART: _em_
SMART_UNDER_EM = r'(?<!\w)(_)(?![\s_]){}(?<!\s)\1(?!\w)'.format(SMART_UNDER_CONTENT)
# SMART: Prioritize _value_ when __value__ is nested within
SMART_UNDER_EM2 = r'(?<![\w_])(_)(?![_\s])(.+?)(?<![_\s])(_)(?![_\w])'
# SMART: _em __strong___
SMART_UNDER_EM_STRONG2 = \
r'(?<!\w)(_)(?![\s_]){}(?<!\w)_{{2}}(?![\s_]){}(?<!\s)_{{3}}(?!\w)'.format(
SMART_UNDER_CONTENT, SMART_UNDER_CONTENT
)

# Smart rules for when "smart asterisk" is enabled
# SMART: ***strong,em***
SMART_STAR_STRONG_EM = r'(?:(?<=_)|(?<![\w\*]))(\*{3})(?![\s\*])%s(?<!\s)\1(?:(?=_)|(?![\w\*]))' % SMART_STAR_CONTENT
# ***strong,em* strong**
SMART_STAR_STRONG_EM = r'(?:(?<=_)|(?<![\w\*]))(\*{{3}})(?![\s\*]){}(?<!\s)\1(?:(?=_)|(?![\w\*]))'.format(
SMART_STAR_CONTENT
)
# SMART: ***strong,em* strong**
SMART_STAR_STRONG_EM2 = \
r'(?:(?<=_)|(?<![\w\*]))(\*{{3}})(?![\s\*]){}(?<!\s)\*(?:(?=_)|(?![\w\*])){}(?<!\s)\*{{2}}(?:(?=_)|(?![\w\*]))'.format(
SMART_STAR_CONTENT, SMART_STAR_CONTENT
)
# ***em,strong** em*
# SMART: ***em,strong** em*
SMART_STAR_EM_STRONG = \
r'(?:(?<=_)|(?<![\w\*]))(\*{{3}})(?![\s\*]){}(?<!\s)\*{{2}}(?:(?=_)|(?![\w\*])){}(?<!\s)\*(?:(?=_)|(?![\w\*]))'.format(
SMART_STAR_CONTENT, SMART_STAR_CONTENT
)
# **strong**
SMART_STAR_STRONG = r'(?:(?<=_)|(?<![\w\*]))(\*{2})(?![\s\*])%s(?<!\s)\1(?:(?=_)|(?![\w\*]))' % SMART_STAR_CONTENT
# SMART *em*
SMART_STAR_EM = r'(?:(?<=_)|(?<![\w\*]))(\*)(?![\s\*])%s(?<!\s)\1(?:(?=_)|(?![\w\*]))' % SMART_STAR_CONTENT
# Prioritize *value* when **value** is nested within
# SMART: **strong**
SMART_STAR_STRONG = r'(?:(?<=_)|(?<![\w\*]))(\*{{2}})(?![\s\*]){}(?<!\s)\1(?:(?=_)|(?![\w\*]))'.format(
SMART_STAR_CONTENT
)
# SMART: *em*
SMART_STAR_EM = r'(?:(?<=_)|(?<![\w\*]))(\*)(?![\s\*]){}(?<!\s)\1(?:(?=_)|(?![\w\*]))'.format(SMART_STAR_CONTENT)
# SMART: Prioritize *value* when **value** is nested within
SMART_STAR_EM2 = r'(?<![\w\*])(\*)(?![\*\s])(.+?)(?<![\*\s])(\*)(?![\*\w])'
# SMART: *em **strong***
SMART_STAR_EM_STRONG2 = \
r'(?:(?<=_)|(?<![\w\*]))(\*)(?![\s\*]){}(?:(?<=_)|(?<![\w\*]))\*{{2}}(?![\s\*]){}(?<!\s)\*{{3}}(?:(?=_)|(?![\w\*]))'.format(
SMART_STAR_CONTENT, SMART_STAR_CONTENT
)


class AsteriskProcessor(util.PatternSequenceProcessor):
Expand All @@ -115,6 +131,7 @@ class AsteriskProcessor(util.PatternSequenceProcessor):
util.PatSeqItem(re.compile(STAR_STRONG_EM3, re.DOTALL | re.UNICODE), 'double2', 'strong,em'),
util.PatSeqItem(re.compile(STAR_STRONG, re.DOTALL | re.UNICODE), 'single', 'strong'),
util.PatSeqItem(re.compile(STAR_EM2, re.DOTALL | re.UNICODE), 'single', 'em', True),
util.PatSeqItem(re.compile(STAR_EM_STRONG2, re.DOTALL | re.UNICODE), 'double2', 'em,strong'),
util.PatSeqItem(re.compile(STAR_EM, re.DOTALL | re.UNICODE), 'single', 'em')
]

Expand All @@ -128,6 +145,7 @@ class SmartAsteriskProcessor(util.PatternSequenceProcessor):
util.PatSeqItem(re.compile(SMART_STAR_STRONG_EM2, re.DOTALL | re.UNICODE), 'double', 'strong,em'),
util.PatSeqItem(re.compile(SMART_STAR_STRONG, re.DOTALL | re.UNICODE), 'single', 'strong'),
util.PatSeqItem(re.compile(SMART_STAR_EM2, re.DOTALL | re.UNICODE), 'single', 'em', True),
util.PatSeqItem(re.compile(SMART_STAR_EM_STRONG2, re.DOTALL | re.UNICODE), 'double2', 'em,strong'),
util.PatSeqItem(re.compile(SMART_STAR_EM, re.DOTALL | re.UNICODE), 'single', 'em')
]

Expand All @@ -142,6 +160,7 @@ class UnderscoreProcessor(util.PatternSequenceProcessor):
util.PatSeqItem(re.compile(UNDER_STRONG_EM3, re.DOTALL | re.UNICODE), 'double2', 'strong,em'),
util.PatSeqItem(re.compile(UNDER_STRONG, re.DOTALL | re.UNICODE), 'single', 'strong'),
util.PatSeqItem(re.compile(UNDER_EM2, re.DOTALL | re.UNICODE), 'single', 'em', True),
util.PatSeqItem(re.compile(UNDER_EM_STRONG2, re.DOTALL | re.UNICODE), 'double2', 'em,strong'),
util.PatSeqItem(re.compile(UNDER_EM, re.DOTALL | re.UNICODE), 'single', 'em')
]

Expand All @@ -155,6 +174,7 @@ class SmartUnderscoreProcessor(util.PatternSequenceProcessor):
util.PatSeqItem(re.compile(SMART_UNDER_STRONG_EM2, re.DOTALL | re.UNICODE), 'double', 'strong,em'),
util.PatSeqItem(re.compile(SMART_UNDER_STRONG, re.DOTALL | re.UNICODE), 'single', 'strong'),
util.PatSeqItem(re.compile(SMART_UNDER_EM2, re.DOTALL | re.UNICODE), 'single', 'em', True),
util.PatSeqItem(re.compile(SMART_UNDER_EM_STRONG2, re.DOTALL | re.UNICODE), 'double2', 'em,strong'),
util.PatSeqItem(re.compile(SMART_UNDER_EM, re.DOTALL | re.UNICODE), 'single', 'em')
]

Expand Down
24 changes: 16 additions & 8 deletions pymdownx/caret.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,28 +46,34 @@
# `^^ins^sup,ins^^^`
INS_SUP3 = r'(\^{{2}})(?![\s\^]){}\^(?![\s\^]){}(?<!\s)\^{{3}}'.format(CONTENT2, CONTENT)
# `^^ins^^`
INS = r'(\^{2})(?!\s)%s(?<!\s)\1' % CONTENT2
INS = r'(\^{{2}})(?!\s){}(?<!\s)\1'.format(CONTENT2)
# `^sup^`
SUP = r'(\^)(?!\s)%s(?<!\s)\1' % CONTENT

SUP = r'(\^)(?!\s){}(?<!\s)\1'.format(CONTENT)
# `^sup ^^sup,ins^^^`
SUP_INS2 = r'(?<!\^)(\^)(?![\^\s]){}\^{{2}}{}\^{{3}}'.format(CONTENT, CONTENT)
# Prioritize ^value^ when ^^value^^ is nested within
SUP2 = r'(?<!\^)(\^)(?![\^\s])([^\s]+?)(?<![\^\s])(\^)(?!\^)'

# Smart rules for when "smart caret" is enabled
# SMART: `^^^ins,sup^^^`
SMART_INS_SUP = r'(\^{3})(?![\s\^])%s(?<!\s)\1' % CONTENT
# `^^^ins,sup^ ins^^`
SMART_INS_SUP = r'(\^{{3}})(?![\s\^]){}(?<!\s)\1'.format(CONTENT)
# SMART: `^^^ins,sup^ ins^^`
SMART_INS_SUP2 = \
r'(\^{{3}})(?![\s\^]){}(?<!\s)\^(?:(?=_)|(?![\w\^])){}(?<!\s)\^{{2}}'.format(
CONTENT, SMART_CONTENT
)
# `^^^sup,ins^^ sup^`
# SMART: `^^^sup,ins^^ sup^`
SMART_SUP_INS = \
r'(\^{{3}})(?![\s\^]){}(?<!\s)\^{{2}}(?:(?=_)|(?![\w\^])){}(?<!\s)\^'.format(
CONTENT, CONTENT
)
# `^^ins^^`
SMART_INS = r'(?:(?<=_)|(?<![\w\^]))(\^{2})(?![\s\^])%s(?<!\s)\1(?:(?=_)|(?![\w\^]))' % SMART_CONTENT
# SMART: `^^ins^^`
SMART_INS = r'(?:(?<=_)|(?<![\w\^]))(\^{{2}})(?![\s\^]){}(?<!\s)\1(?:(?=_)|(?![\w\^]))'.format(SMART_CONTENT)
# SMART: `^sup ^^sup,ins^^^`
SMART_SUP_INS2 = \
r'(?<!\^)(\^)(?![\s\^]){}(?:(?<=_)|(?<![\w\^]))\^{{2}}(?![\s\^]){}(?<!\s)\^{{3}}'.format(
CONTENT, CONTENT
)


class CaretProcessor(util.PatternSequenceProcessor):
Expand All @@ -80,6 +86,7 @@ class CaretProcessor(util.PatternSequenceProcessor):
util.PatSeqItem(re.compile(INS_SUP3, re.DOTALL | re.UNICODE), 'double2', 'ins,sup'),
util.PatSeqItem(re.compile(INS, re.DOTALL | re.UNICODE), 'single', 'ins'),
util.PatSeqItem(re.compile(SUP2, re.DOTALL | re.UNICODE), 'single', 'sup', True),
util.PatSeqItem(re.compile(SUP_INS2, re.DOTALL | re.UNICODE), 'double2', 'sup,ins'),
util.PatSeqItem(re.compile(SUP, re.DOTALL | re.UNICODE), 'single', 'sup')
]

Expand All @@ -93,6 +100,7 @@ class CaretSmartProcessor(util.PatternSequenceProcessor):
util.PatSeqItem(re.compile(SMART_INS_SUP2, re.DOTALL | re.UNICODE), 'double', 'ins,sup'),
util.PatSeqItem(re.compile(SMART_INS, re.DOTALL | re.UNICODE), 'single', 'ins'),
util.PatSeqItem(re.compile(SUP2, re.DOTALL | re.UNICODE), 'single', 'sup', True),
util.PatSeqItem(re.compile(SMART_SUP_INS2, re.DOTALL | re.UNICODE), 'double2', 'sup,ins'),
util.PatSeqItem(re.compile(SUP, re.DOTALL | re.UNICODE), 'single', 'sup')
]

Expand Down
4 changes: 2 additions & 2 deletions pymdownx/mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@
NOT_MARK = r'((^|(?<=\s))(=+)(?=\s|$))'

# ==mark==
MARK = r'(={2})(?!\s)%s(?<!\s)\1' % CONTENT
MARK = r'(={{2}})(?!\s){}(?<!\s)\1'.format(CONTENT)
# ==mark==
SMART_MARK = r'(?:(?<=_)|(?<![\w=]))(={2})(?![\s=])%s(?<!\s)\1(?:(?=_)|(?![\w=]))' % SMART_CONTENT
SMART_MARK = r'(?:(?<=_)|(?<![\w=]))(={{2}})(?![\s=]){}(?<!\s)\1(?:(?=_)|(?![\w=]))'.format(SMART_CONTENT)


class MarkProcessor(util.PatternSequenceProcessor):
Expand Down
24 changes: 16 additions & 8 deletions pymdownx/tilde.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,34 @@
# `~~del~sub,del~~~`
DEL_SUB3 = r'(~{{2}})(?![\s~]){}~(?![\s~]){}(?<!\s)~{{3}}'.format(CONTENT2, CONTENT)
# `~~del~~`
DEL = r'(~{2})(?!\s)%s(?<!\s)\1' % CONTENT2
DEL = r'(~{{2}})(?!\s){}(?<!\s)\1'.format(CONTENT2)
# `~sub~`
SUB = r'(~)(?!\s)%s(?<!\s)\1' % CONTENT

SUB = r'(~)(?!\s){}(?<!\s)\1'.format(CONTENT)
# `~sub ~~sub,del~~~`
SUB_DEL2 = r'(?<!~)(~)(?![~\s]){}~{{2}}{}~{{3}}'.format(CONTENT, CONTENT)
# Prioritize ~value~ when ~~value~~ is nested within
SUB2 = r'(?<!~)(~)(?![~\s])([^\s]+?)(?<![~\s])(~)(?!~)'

# Smart rules for when "smart tilde" is enabled
# SMART: `~~~del,sub~~~`
SMART_DEL_SUB = r'(~{3})(?![\s~])%s(?<!\s)\1' % CONTENT
# `~~~del,sub~ del~~`
SMART_DEL_SUB = r'(~{{3}})(?![\s~]){}(?<!\s)\1'.format(CONTENT)
# SMART: `~~~del,sub~ del~~`
SMART_DEL_SUB2 = \
r'(~{{3}})(?![\s~]){}(?<!\s)~(?:(?=_)|(?![\w~])){}(?<!\s)~{{2}}'.format(
CONTENT, SMART_CONTENT
)
# `~~~sub,del~~ sub~`
# SMART: `~~~sub,del~~ sub~`
SMART_SUB_DEL = \
r'(~{{3}})(?![\s~]){}(?<!\s)~{{2}}(?:(?=_)|(?![\w~])){}(?<!\s)~'.format(
CONTENT, CONTENT
)
# `~~del~~`
SMART_DEL = r'(?:(?<=_)|(?<![\w~]))(~{2})(?![\s~])%s(?<!\s)\1(?:(?=_)|(?![\w~]))' % SMART_CONTENT
# SMART: `~~del~~`
SMART_DEL = r'(?:(?<=_)|(?<![\w~]))(~{{2}})(?![\s~]){}(?<!\s)\1(?:(?=_)|(?![\w~]))'.format(SMART_CONTENT)
# SMART: `~sub ~~sub,del~~~`
SMART_SUB_DEL2 = \
r'(?<!~)(~)(?![\s~]){}(?:(?<=_)|(?<![\w~]))~{{2}}(?![\s~]){}(?<!\s)~{{3}}'.format(
CONTENT, CONTENT
)


class TildeProcessor(util.PatternSequenceProcessor):
Expand All @@ -79,6 +85,7 @@ class TildeProcessor(util.PatternSequenceProcessor):
util.PatSeqItem(re.compile(DEL_SUB3, re.DOTALL | re.UNICODE), 'double2', 'del,sub'),
util.PatSeqItem(re.compile(DEL, re.DOTALL | re.UNICODE), 'single', 'del'),
util.PatSeqItem(re.compile(SUB2, re.DOTALL | re.UNICODE), 'single', 'sub', True),
util.PatSeqItem(re.compile(SUB_DEL2, re.DOTALL | re.UNICODE), 'double2', 'sub,del'),
util.PatSeqItem(re.compile(SUB, re.DOTALL | re.UNICODE), 'single', 'sub')
]

Expand All @@ -92,6 +99,7 @@ class TildeSmartProcessor(util.PatternSequenceProcessor):
util.PatSeqItem(re.compile(SMART_DEL_SUB2, re.DOTALL | re.UNICODE), 'double', 'del,sub'),
util.PatSeqItem(re.compile(SMART_DEL, re.DOTALL | re.UNICODE), 'single', 'del'),
util.PatSeqItem(re.compile(SUB2, re.DOTALL | re.UNICODE), 'single', 'sub', True),
util.PatSeqItem(re.compile(SMART_SUB_DEL2, re.DOTALL | re.UNICODE), 'double2', 'sub,del'),
util.PatSeqItem(re.compile(SUB, re.DOTALL | re.UNICODE), 'single', 'sub')
]

Expand Down
10 changes: 0 additions & 10 deletions tests/extensions/caret/caret (dumb).html

This file was deleted.

19 changes: 0 additions & 19 deletions tests/extensions/caret/caret (dumb).txt

This file was deleted.

11 changes: 0 additions & 11 deletions tests/extensions/caret/caret.html

This file was deleted.

21 changes: 0 additions & 21 deletions tests/extensions/caret/caret.txt

This file was deleted.

9 changes: 0 additions & 9 deletions tests/extensions/caret/tests.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
__default__: {}

caret:
extensions:
pymdownx.caret:

caret (dumb):
extensions:
pymdownx.caret:
smart_insert: false

caret (dumb no sup):
extensions:
pymdownx.caret:
Expand Down
9 changes: 0 additions & 9 deletions tests/extensions/tilde/tests.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
__default__: {}

tilde:
extensions:
pymdownx.tilde:

tilde (dumb):
extensions:
pymdownx.tilde:
smart_delete: false

tilde (dumb no sub):
extensions:
pymdownx.tilde:
Expand Down
11 changes: 0 additions & 11 deletions tests/extensions/tilde/tilde (dumb).html

This file was deleted.

Loading

0 comments on commit 7a95865

Please sign in to comment.