Fix being able to set bold, italic and strikethrough at the same time.
This commit is contained in:
parent
37332b5618
commit
1527937f8b
2 changed files with 57 additions and 25 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
# 0.2.1.7
|
# 0.2.1.7
|
||||||
|
|
||||||
|
* Fix being able to set bold, italic and strikethrough at the same time.
|
||||||
* Add AppImage
|
* Add AppImage
|
||||||
|
|
||||||
# 0.2.1.6
|
# 0.2.1.6
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,11 @@ class MarkdownHighlighter(QSyntaxHighlighter):
|
||||||
self.strike_format = QTextCharFormat()
|
self.strike_format = QTextCharFormat()
|
||||||
self.strike_format.setFontStrikeOut(True)
|
self.strike_format.setFontStrikeOut(True)
|
||||||
|
|
||||||
|
# Allow combination of bold/italic
|
||||||
|
self.bold_italic_format = QTextCharFormat()
|
||||||
|
self.bold_italic_format.setFontWeight(QFont.Weight.Bold)
|
||||||
|
self.bold_italic_format.setFontItalic(True)
|
||||||
|
|
||||||
# Inline code: `code`
|
# Inline code: `code`
|
||||||
mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
||||||
self.code_format = QTextCharFormat()
|
self.code_format = QTextCharFormat()
|
||||||
|
|
@ -88,6 +93,18 @@ class MarkdownHighlighter(QSyntaxHighlighter):
|
||||||
# Also make them very faint in case they still show
|
# Also make them very faint in case they still show
|
||||||
self.syntax_format.setForeground(QColor(250, 250, 250))
|
self.syntax_format.setForeground(QColor(250, 250, 250))
|
||||||
|
|
||||||
|
def _overlay_range(
|
||||||
|
self, start: int, length: int, overlay_fmt: QTextCharFormat
|
||||||
|
) -> None:
|
||||||
|
"""Merge overlay_fmt onto the existing format for each char in [start, start+length)."""
|
||||||
|
end = start + length
|
||||||
|
i = start
|
||||||
|
while i < end:
|
||||||
|
base = QTextCharFormat(self.format(i)) # current format at this position
|
||||||
|
base.merge(overlay_fmt) # add only the properties we set
|
||||||
|
self.setFormat(i, 1, base) # write back one char
|
||||||
|
i += 1
|
||||||
|
|
||||||
def highlightBlock(self, text: str):
|
def highlightBlock(self, text: str):
|
||||||
"""Apply formatting to a block of text based on markdown syntax."""
|
"""Apply formatting to a block of text based on markdown syntax."""
|
||||||
|
|
||||||
|
|
@ -141,51 +158,65 @@ class MarkdownHighlighter(QSyntaxHighlighter):
|
||||||
self.setFormat(marker_len, len(text) - marker_len, heading_fmt)
|
self.setFormat(marker_len, len(text) - marker_len, heading_fmt)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Bold: **text** or __text__
|
# Bold+Italic: ***text*** or ___text___
|
||||||
for match in re.finditer(r"\*\*(.+?)\*\*|__(.+?)__", text):
|
# Do these first and remember their spans so later passes don't override them.
|
||||||
start, end = match.span()
|
occupied = []
|
||||||
content_start = start + 2
|
for m in re.finditer(
|
||||||
content_end = end - 2
|
r"(?<!\*)\*\*\*(.+?)(?<!\*)\*\*\*|(?<!_)___(.+?)(?<!_)___", text
|
||||||
|
):
|
||||||
|
start, end = m.span()
|
||||||
|
content_start, content_end = start + 3, end - 3
|
||||||
|
self.setFormat(start, 3, self.syntax_format) # hide leading ***
|
||||||
|
self.setFormat(end - 3, 3, self.syntax_format) # hide trailing ***
|
||||||
|
self.setFormat(
|
||||||
|
content_start, content_end - content_start, self.bold_italic_format
|
||||||
|
)
|
||||||
|
occupied.append((start, end))
|
||||||
|
|
||||||
# Gray out the markers
|
def _overlaps(a, b):
|
||||||
|
return not (a[1] <= b[0] or b[1] <= a[0])
|
||||||
|
|
||||||
|
# Bold: **text** or __text__ (but not part of *** or ___)
|
||||||
|
for m in re.finditer(
|
||||||
|
r"(?<!\*)\*\*(?!\*)(.+?)(?<!\*)\*\*(?!\*)|(?<!_)__(?!_)(.+?)(?<!_)__(?!_)",
|
||||||
|
text,
|
||||||
|
):
|
||||||
|
start, end = m.span()
|
||||||
|
if any(_overlaps((start, end), occ) for occ in occupied):
|
||||||
|
continue
|
||||||
|
content_start, content_end = start + 2, end - 2
|
||||||
self.setFormat(start, 2, self.syntax_format)
|
self.setFormat(start, 2, self.syntax_format)
|
||||||
self.setFormat(end - 2, 2, self.syntax_format)
|
self.setFormat(end - 2, 2, self.syntax_format)
|
||||||
|
|
||||||
# Bold the content
|
|
||||||
self.setFormat(content_start, content_end - content_start, self.bold_format)
|
self.setFormat(content_start, content_end - content_start, self.bold_format)
|
||||||
|
|
||||||
# Italic: *text* or _text_ (but not part of bold)
|
# Italic: *text* or _text_ (but not part of bold/** and not inside *** or ___)
|
||||||
for match in re.finditer(
|
for m in re.finditer(
|
||||||
r"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|(?<!_)_(?!_)(.+?)(?<!_)_(?!_)", text
|
r"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|(?<!_)_(?!_)(.+?)(?<!_)_(?!_)", text
|
||||||
):
|
):
|
||||||
start, end = match.span()
|
start, end = m.span()
|
||||||
# Skip if this is part of a bold pattern
|
if any(_overlaps((start, end), occ) for occ in occupied):
|
||||||
|
continue
|
||||||
|
# Keep your existing guards that avoid grabbing * from **:
|
||||||
if start > 0 and text[start - 1 : start + 1] in ("**", "__"):
|
if start > 0 and text[start - 1 : start + 1] in ("**", "__"):
|
||||||
continue
|
continue
|
||||||
if end < len(text) and text[end : end + 1] in ("*", "_"):
|
if end < len(text) and text[end : end + 1] in ("*", "_"):
|
||||||
continue
|
continue
|
||||||
|
content_start, content_end = start + 1, end - 1
|
||||||
content_start = start + 1
|
|
||||||
content_end = end - 1
|
|
||||||
|
|
||||||
# Gray out markers
|
|
||||||
self.setFormat(start, 1, self.syntax_format)
|
self.setFormat(start, 1, self.syntax_format)
|
||||||
self.setFormat(end - 1, 1, self.syntax_format)
|
self.setFormat(end - 1, 1, self.syntax_format)
|
||||||
|
|
||||||
# Italicize content
|
|
||||||
self.setFormat(
|
self.setFormat(
|
||||||
content_start, content_end - content_start, self.italic_format
|
content_start, content_end - content_start, self.italic_format
|
||||||
)
|
)
|
||||||
|
|
||||||
# Strikethrough: ~~text~~
|
# Strikethrough: ~~text~~
|
||||||
for match in re.finditer(r"~~(.+?)~~", text):
|
for m in re.finditer(r"~~(.+?)~~", text):
|
||||||
start, end = match.span()
|
start, end = m.span()
|
||||||
content_start = start + 2
|
content_start, content_end = start + 2, end - 2
|
||||||
content_end = end - 2
|
# Fade the markers
|
||||||
|
|
||||||
self.setFormat(start, 2, self.syntax_format)
|
self.setFormat(start, 2, self.syntax_format)
|
||||||
self.setFormat(end - 2, 2, self.syntax_format)
|
self.setFormat(end - 2, 2, self.syntax_format)
|
||||||
self.setFormat(
|
# Merge strikeout with whatever is already applied (bold, italic, both, links, etc.)
|
||||||
|
self._overlay_range(
|
||||||
content_start, content_end - content_start, self.strike_format
|
content_start, content_end - content_start, self.strike_format
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue