Editor tweaks
This commit is contained in:
parent
bfd0314109
commit
1b706dec18
4 changed files with 269 additions and 210 deletions
|
|
@ -5,204 +5,20 @@ import re
|
|||
from pathlib import Path
|
||||
|
||||
from PySide6.QtGui import (
|
||||
QColor,
|
||||
QFont,
|
||||
QFontDatabase,
|
||||
QFontMetrics,
|
||||
QImage,
|
||||
QPalette,
|
||||
QGuiApplication,
|
||||
QTextCharFormat,
|
||||
QTextCursor,
|
||||
QTextDocument,
|
||||
QSyntaxHighlighter,
|
||||
QTextFormat,
|
||||
QTextImageFormat,
|
||||
)
|
||||
from PySide6.QtCore import Qt, QRect
|
||||
from PySide6.QtCore import Qt, QRect, QTimer
|
||||
from PySide6.QtWidgets import QTextEdit
|
||||
|
||||
from .theme import ThemeManager, Theme
|
||||
|
||||
|
||||
class MarkdownHighlighter(QSyntaxHighlighter):
|
||||
"""Live syntax highlighter for markdown that applies formatting as you type."""
|
||||
|
||||
def __init__(self, document: QTextDocument, theme_manager: ThemeManager):
|
||||
super().__init__(document)
|
||||
self.theme_manager = theme_manager
|
||||
self._setup_formats()
|
||||
# Recompute formats whenever the app theme changes
|
||||
self.theme_manager.themeChanged.connect(self._on_theme_changed)
|
||||
|
||||
def _on_theme_changed(self, *_):
|
||||
self._setup_formats()
|
||||
self.rehighlight()
|
||||
|
||||
def _setup_formats(self):
|
||||
"""Setup text formats for different markdown elements."""
|
||||
|
||||
# Bold: **text** or __text__
|
||||
self.bold_format = QTextCharFormat()
|
||||
self.bold_format.setFontWeight(QFont.Weight.Bold)
|
||||
|
||||
# Italic: *text* or _text_
|
||||
self.italic_format = QTextCharFormat()
|
||||
self.italic_format.setFontItalic(True)
|
||||
|
||||
# Strikethrough: ~~text~~
|
||||
self.strike_format = QTextCharFormat()
|
||||
self.strike_format.setFontStrikeOut(True)
|
||||
|
||||
# Inline code: `code`
|
||||
mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
||||
self.code_format = QTextCharFormat()
|
||||
self.code_format.setFont(mono)
|
||||
self.code_format.setFontFixedPitch(True)
|
||||
|
||||
# Code block: ```
|
||||
self.code_block_format = QTextCharFormat()
|
||||
self.code_block_format.setFont(mono)
|
||||
self.code_block_format.setFontFixedPitch(True)
|
||||
|
||||
pal = QGuiApplication.palette()
|
||||
if self.theme_manager.current() == Theme.DARK:
|
||||
# In dark mode, use a darker panel-like background
|
||||
bg = pal.color(QPalette.AlternateBase)
|
||||
fg = pal.color(QPalette.Text)
|
||||
else:
|
||||
# Light mode: keep the existing light gray
|
||||
bg = QColor(245, 245, 245)
|
||||
fg = pal.color(QPalette.Text)
|
||||
self.code_block_format.setBackground(bg)
|
||||
self.code_block_format.setForeground(fg)
|
||||
|
||||
# Headings
|
||||
self.h1_format = QTextCharFormat()
|
||||
self.h1_format.setFontPointSize(24.0)
|
||||
self.h1_format.setFontWeight(QFont.Weight.Bold)
|
||||
|
||||
self.h2_format = QTextCharFormat()
|
||||
self.h2_format.setFontPointSize(18.0)
|
||||
self.h2_format.setFontWeight(QFont.Weight.Bold)
|
||||
|
||||
self.h3_format = QTextCharFormat()
|
||||
self.h3_format.setFontPointSize(14.0)
|
||||
self.h3_format.setFontWeight(QFont.Weight.Bold)
|
||||
|
||||
# Markdown syntax (the markers themselves) - make invisible
|
||||
self.syntax_format = QTextCharFormat()
|
||||
# Make the markers invisible by setting font size to 0.1 points
|
||||
self.syntax_format.setFontPointSize(0.1)
|
||||
# Also make them very faint in case they still show
|
||||
self.syntax_format.setForeground(QColor(250, 250, 250))
|
||||
|
||||
def highlightBlock(self, text: str):
|
||||
"""Apply formatting to a block of text based on markdown syntax."""
|
||||
if not text:
|
||||
return
|
||||
|
||||
# Track if we're in a code block (multiline)
|
||||
prev_state = self.previousBlockState()
|
||||
in_code_block = prev_state == 1
|
||||
|
||||
# Check for code block fences
|
||||
if text.strip().startswith("```"):
|
||||
# background for the whole fence line (so block looks continuous)
|
||||
self.setFormat(0, len(text), self.code_block_format)
|
||||
|
||||
# hide the three backticks themselves
|
||||
idx = text.find("```")
|
||||
if idx != -1:
|
||||
self.setFormat(idx, 3, self.syntax_format)
|
||||
|
||||
# toggle code-block state and stop; next line picks up state
|
||||
in_code_block = not in_code_block
|
||||
self.setCurrentBlockState(1 if in_code_block else 0)
|
||||
return
|
||||
|
||||
if in_code_block:
|
||||
# Format entire line as code
|
||||
self.setFormat(0, len(text), self.code_block_format)
|
||||
self.setCurrentBlockState(1)
|
||||
return
|
||||
|
||||
self.setCurrentBlockState(0)
|
||||
|
||||
# Headings (must be at start of line)
|
||||
heading_match = re.match(r"^(#{1,3})\s+", text)
|
||||
if heading_match:
|
||||
level = len(heading_match.group(1))
|
||||
marker_len = len(heading_match.group(0))
|
||||
|
||||
# Format the # markers
|
||||
self.setFormat(0, marker_len, self.syntax_format)
|
||||
|
||||
# Format the heading text
|
||||
heading_fmt = (
|
||||
self.h1_format
|
||||
if level == 1
|
||||
else self.h2_format if level == 2 else self.h3_format
|
||||
)
|
||||
self.setFormat(marker_len, len(text) - marker_len, heading_fmt)
|
||||
return
|
||||
|
||||
# Bold: **text** or __text__
|
||||
for match in re.finditer(r"\*\*(.+?)\*\*|__(.+?)__", text):
|
||||
start, end = match.span()
|
||||
content_start = start + 2
|
||||
content_end = end - 2
|
||||
|
||||
# Gray out the markers
|
||||
self.setFormat(start, 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)
|
||||
|
||||
# Italic: *text* or _text_ (but not part of bold)
|
||||
for match in re.finditer(
|
||||
r"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|(?<!_)_(?!_)(.+?)(?<!_)_(?!_)", text
|
||||
):
|
||||
start, end = match.span()
|
||||
# Skip if this is part of a bold pattern
|
||||
if start > 0 and text[start - 1 : start + 1] in ("**", "__"):
|
||||
continue
|
||||
if end < len(text) and text[end : end + 1] in ("*", "_"):
|
||||
continue
|
||||
|
||||
content_start = start + 1
|
||||
content_end = end - 1
|
||||
|
||||
# Gray out markers
|
||||
self.setFormat(start, 1, self.syntax_format)
|
||||
self.setFormat(end - 1, 1, self.syntax_format)
|
||||
|
||||
# Italicize content
|
||||
self.setFormat(
|
||||
content_start, content_end - content_start, self.italic_format
|
||||
)
|
||||
|
||||
# Strikethrough: ~~text~~
|
||||
for match in re.finditer(r"~~(.+?)~~", text):
|
||||
start, end = match.span()
|
||||
content_start = start + 2
|
||||
content_end = end - 2
|
||||
|
||||
self.setFormat(start, 2, self.syntax_format)
|
||||
self.setFormat(end - 2, 2, self.syntax_format)
|
||||
self.setFormat(
|
||||
content_start, content_end - content_start, self.strike_format
|
||||
)
|
||||
|
||||
# Inline code: `code`
|
||||
for match in re.finditer(r"`([^`]+)`", text):
|
||||
start, end = match.span()
|
||||
content_start = start + 1
|
||||
content_end = end - 1
|
||||
|
||||
self.setFormat(start, 1, self.syntax_format)
|
||||
self.setFormat(end - 1, 1, self.syntax_format)
|
||||
self.setFormat(content_start, content_end - content_start, self.code_format)
|
||||
from .theme import ThemeManager
|
||||
from .markdown_highlighter import MarkdownHighlighter
|
||||
|
||||
|
||||
class MarkdownEditor(QTextEdit):
|
||||
|
|
@ -244,6 +60,10 @@ class MarkdownEditor(QTextEdit):
|
|||
|
||||
# Connect to text changes for smart formatting
|
||||
self.textChanged.connect(self._on_text_changed)
|
||||
self.textChanged.connect(self._update_code_block_row_backgrounds)
|
||||
self.theme_manager.themeChanged.connect(
|
||||
lambda *_: self._update_code_block_row_backgrounds()
|
||||
)
|
||||
|
||||
# Enable mouse tracking for checkbox clicking
|
||||
self.viewport().setMouseTracking(True)
|
||||
|
|
@ -253,6 +73,12 @@ class MarkdownEditor(QTextEdit):
|
|||
# reattach the highlighter to the new document
|
||||
if hasattr(self, "highlighter") and self.highlighter:
|
||||
self.highlighter.setDocument(self.document())
|
||||
QTimer.singleShot(0, self._update_code_block_row_backgrounds)
|
||||
|
||||
def showEvent(self, e):
|
||||
super().showEvent(e)
|
||||
# First time the widget is shown, Qt may rebuild layout once more.
|
||||
QTimer.singleShot(0, self._update_code_block_row_backgrounds)
|
||||
|
||||
def _on_text_changed(self):
|
||||
"""Handle live formatting updates - convert checkbox markdown to Unicode."""
|
||||
|
|
@ -301,6 +127,51 @@ class MarkdownEditor(QTextEdit):
|
|||
finally:
|
||||
self._updating = False
|
||||
|
||||
def _update_code_block_row_backgrounds(self):
|
||||
"""Paint a full-width background for each line that is in a fenced code block."""
|
||||
doc = self.document()
|
||||
sels = []
|
||||
|
||||
# Use the same bg color as the highlighter's code block
|
||||
bg_brush = self.highlighter.code_block_format.background()
|
||||
|
||||
inside = False
|
||||
block = doc.begin()
|
||||
while block.isValid():
|
||||
text = block.text()
|
||||
stripped = text.strip()
|
||||
is_fence = stripped.startswith("```")
|
||||
|
||||
paint_this_line = is_fence or inside
|
||||
if paint_this_line:
|
||||
sel = QTextEdit.ExtraSelection()
|
||||
fmt = QTextCharFormat()
|
||||
fmt.setBackground(bg_brush)
|
||||
fmt.setProperty(QTextFormat.FullWidthSelection, True)
|
||||
# mark so we can merge with other selections safely
|
||||
fmt.setProperty(QTextFormat.UserProperty, "codeblock_bg")
|
||||
sel.format = fmt
|
||||
|
||||
cur = QTextCursor(doc)
|
||||
cur.setPosition(
|
||||
block.position()
|
||||
) # collapsed cursor = whole line when FullWidthSelection
|
||||
sel.cursor = cur
|
||||
|
||||
sels.append(sel)
|
||||
|
||||
if is_fence:
|
||||
inside = not inside
|
||||
|
||||
block = block.next()
|
||||
|
||||
others = [
|
||||
s
|
||||
for s in self.extraSelections()
|
||||
if s.format.property(QTextFormat.UserProperty) != "codeblock_bg"
|
||||
]
|
||||
self.setExtraSelections(others + sels)
|
||||
|
||||
def to_markdown(self) -> str:
|
||||
"""Export current content as markdown."""
|
||||
# First, extract any embedded images and convert to markdown
|
||||
|
|
@ -377,6 +248,9 @@ class MarkdownEditor(QTextEdit):
|
|||
# Render any embedded images
|
||||
self._render_images()
|
||||
|
||||
self._update_code_block_row_backgrounds()
|
||||
QTimer.singleShot(0, self._update_code_block_row_backgrounds)
|
||||
|
||||
def _render_images(self):
|
||||
"""Find and render base64 images in the document."""
|
||||
text = self.toPlainText()
|
||||
|
|
|
|||
200
bouquin/markdown_highlighter.py
Normal file
200
bouquin/markdown_highlighter.py
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from PySide6.QtGui import (
|
||||
QColor,
|
||||
QFont,
|
||||
QFontDatabase,
|
||||
QGuiApplication,
|
||||
QPalette,
|
||||
QSyntaxHighlighter,
|
||||
QTextCharFormat,
|
||||
QTextDocument,
|
||||
)
|
||||
|
||||
from .theme import ThemeManager, Theme
|
||||
|
||||
|
||||
class MarkdownHighlighter(QSyntaxHighlighter):
|
||||
"""Live syntax highlighter for markdown that applies formatting as you type."""
|
||||
|
||||
def __init__(self, document: QTextDocument, theme_manager: ThemeManager):
|
||||
super().__init__(document)
|
||||
self.theme_manager = theme_manager
|
||||
self._setup_formats()
|
||||
# Recompute formats whenever the app theme changes
|
||||
self.theme_manager.themeChanged.connect(self._on_theme_changed)
|
||||
|
||||
def _on_theme_changed(self, *_):
|
||||
self._setup_formats()
|
||||
self.rehighlight()
|
||||
|
||||
def _setup_formats(self):
|
||||
"""Setup text formats for different markdown elements."""
|
||||
|
||||
# Bold: **text** or __text__
|
||||
self.bold_format = QTextCharFormat()
|
||||
self.bold_format.setFontWeight(QFont.Weight.Bold)
|
||||
|
||||
# Italic: *text* or _text_
|
||||
self.italic_format = QTextCharFormat()
|
||||
self.italic_format.setFontItalic(True)
|
||||
|
||||
# Strikethrough: ~~text~~
|
||||
self.strike_format = QTextCharFormat()
|
||||
self.strike_format.setFontStrikeOut(True)
|
||||
|
||||
# Inline code: `code`
|
||||
mono = QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
||||
self.code_format = QTextCharFormat()
|
||||
self.code_format.setFont(mono)
|
||||
self.code_format.setFontFixedPitch(True)
|
||||
|
||||
# Code block: ```
|
||||
self.code_block_format = QTextCharFormat()
|
||||
self.code_block_format.setFont(mono)
|
||||
self.code_block_format.setFontFixedPitch(True)
|
||||
|
||||
pal = QGuiApplication.palette()
|
||||
if self.theme_manager.current() == Theme.DARK:
|
||||
# In dark mode, use a darker panel-like background
|
||||
bg = pal.color(QPalette.AlternateBase)
|
||||
fg = pal.color(QPalette.Text)
|
||||
else:
|
||||
# Light mode: keep the existing light gray
|
||||
bg = QColor(245, 245, 245)
|
||||
fg = pal.color(QPalette.Text)
|
||||
self.code_block_format.setBackground(bg)
|
||||
self.code_block_format.setForeground(fg)
|
||||
|
||||
# Headings
|
||||
self.h1_format = QTextCharFormat()
|
||||
self.h1_format.setFontPointSize(24.0)
|
||||
self.h1_format.setFontWeight(QFont.Weight.Bold)
|
||||
|
||||
self.h2_format = QTextCharFormat()
|
||||
self.h2_format.setFontPointSize(18.0)
|
||||
self.h2_format.setFontWeight(QFont.Weight.Bold)
|
||||
|
||||
self.h3_format = QTextCharFormat()
|
||||
self.h3_format.setFontPointSize(14.0)
|
||||
self.h3_format.setFontWeight(QFont.Weight.Bold)
|
||||
|
||||
# Markdown syntax (the markers themselves) - make invisible
|
||||
self.syntax_format = QTextCharFormat()
|
||||
# Make the markers invisible by setting font size to 0.1 points
|
||||
self.syntax_format.setFontPointSize(0.1)
|
||||
# Also make them very faint in case they still show
|
||||
self.syntax_format.setForeground(QColor(250, 250, 250))
|
||||
|
||||
def highlightBlock(self, text: str):
|
||||
"""Apply formatting to a block of text based on markdown syntax."""
|
||||
|
||||
# Track if we're in a code block (multiline)
|
||||
prev_state = self.previousBlockState()
|
||||
in_code_block = prev_state == 1
|
||||
|
||||
# Check for code block fences
|
||||
if text.strip().startswith("```"):
|
||||
# background for the whole fence line (so block looks continuous)
|
||||
self.setFormat(0, len(text), self.code_block_format)
|
||||
|
||||
# hide the three backticks themselves
|
||||
idx = text.find("```")
|
||||
if idx != -1:
|
||||
self.setFormat(idx, 3, self.syntax_format)
|
||||
|
||||
# toggle code-block state and stop; next line picks up state
|
||||
in_code_block = not in_code_block
|
||||
self.setCurrentBlockState(1 if in_code_block else 0)
|
||||
return
|
||||
|
||||
if in_code_block:
|
||||
# inside code: apply block bg and language rules
|
||||
self.setFormat(0, len(text), self.code_block_format)
|
||||
self.setCurrentBlockState(1)
|
||||
return
|
||||
|
||||
# ---- Normal markdown (outside code)
|
||||
self.setCurrentBlockState(0)
|
||||
|
||||
# If the line is empty and not in a code block, nothing else to do
|
||||
if not text:
|
||||
return
|
||||
|
||||
# Headings (must be at start of line)
|
||||
heading_match = re.match(r"^(#{1,3})\s+", text)
|
||||
if heading_match:
|
||||
level = len(heading_match.group(1))
|
||||
marker_len = len(heading_match.group(0))
|
||||
|
||||
# Format the # markers
|
||||
self.setFormat(0, marker_len, self.syntax_format)
|
||||
|
||||
# Format the heading text
|
||||
heading_fmt = (
|
||||
self.h1_format
|
||||
if level == 1
|
||||
else self.h2_format if level == 2 else self.h3_format
|
||||
)
|
||||
self.setFormat(marker_len, len(text) - marker_len, heading_fmt)
|
||||
return
|
||||
|
||||
# Bold: **text** or __text__
|
||||
for match in re.finditer(r"\*\*(.+?)\*\*|__(.+?)__", text):
|
||||
start, end = match.span()
|
||||
content_start = start + 2
|
||||
content_end = end - 2
|
||||
|
||||
# Gray out the markers
|
||||
self.setFormat(start, 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)
|
||||
|
||||
# Italic: *text* or _text_ (but not part of bold)
|
||||
for match in re.finditer(
|
||||
r"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|(?<!_)_(?!_)(.+?)(?<!_)_(?!_)", text
|
||||
):
|
||||
start, end = match.span()
|
||||
# Skip if this is part of a bold pattern
|
||||
if start > 0 and text[start - 1 : start + 1] in ("**", "__"):
|
||||
continue
|
||||
if end < len(text) and text[end : end + 1] in ("*", "_"):
|
||||
continue
|
||||
|
||||
content_start = start + 1
|
||||
content_end = end - 1
|
||||
|
||||
# Gray out markers
|
||||
self.setFormat(start, 1, self.syntax_format)
|
||||
self.setFormat(end - 1, 1, self.syntax_format)
|
||||
|
||||
# Italicize content
|
||||
self.setFormat(
|
||||
content_start, content_end - content_start, self.italic_format
|
||||
)
|
||||
|
||||
# Strikethrough: ~~text~~
|
||||
for match in re.finditer(r"~~(.+?)~~", text):
|
||||
start, end = match.span()
|
||||
content_start = start + 2
|
||||
content_end = end - 2
|
||||
|
||||
self.setFormat(start, 2, self.syntax_format)
|
||||
self.setFormat(end - 2, 2, self.syntax_format)
|
||||
self.setFormat(
|
||||
content_start, content_end - content_start, self.strike_format
|
||||
)
|
||||
|
||||
# Inline code: `code`
|
||||
for match in re.finditer(r"`([^`]+)`", text):
|
||||
start, end = match.span()
|
||||
content_start = start + 1
|
||||
content_end = end - 1
|
||||
|
||||
self.setFormat(start, 1, self.syntax_format)
|
||||
self.setFormat(end - 1, 1, self.syntax_format)
|
||||
self.setFormat(content_start, content_end - content_start, self.code_format)
|
||||
|
|
@ -83,10 +83,7 @@ class Search(QWidget):
|
|||
|
||||
for date_str, content in rows:
|
||||
# Build an HTML fragment around the match and whether to show ellipses
|
||||
frag_html, left_ell, right_ell = self._make_html_snippet(
|
||||
content, query, radius=30, maxlen=90
|
||||
)
|
||||
|
||||
frag_html = self._make_html_snippet(content, query, radius=30, maxlen=90)
|
||||
# ---- Per-item widget: date on top, preview row below (with ellipses) ----
|
||||
container = QWidget()
|
||||
outer = QVBoxLayout(container)
|
||||
|
|
@ -108,11 +105,6 @@ class Search(QWidget):
|
|||
h.setContentsMargins(0, 0, 0, 0)
|
||||
h.setSpacing(4)
|
||||
|
||||
if left_ell:
|
||||
left = QLabel("…")
|
||||
left.setStyleSheet("color:#888;")
|
||||
h.addWidget(left, 0, Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
preview = QLabel()
|
||||
preview.setTextFormat(Qt.TextFormat.RichText)
|
||||
preview.setWordWrap(True)
|
||||
|
|
@ -124,11 +116,6 @@ class Search(QWidget):
|
|||
)
|
||||
h.addWidget(preview, 1)
|
||||
|
||||
if right_ell:
|
||||
right = QLabel("…")
|
||||
right.setStyleSheet("color:#888;")
|
||||
h.addWidget(right, 0, Qt.AlignmentFlag.AlignBottom)
|
||||
|
||||
outer.addWidget(row)
|
||||
|
||||
line = QFrame()
|
||||
|
|
@ -145,9 +132,7 @@ class Search(QWidget):
|
|||
self.results.setItemWidget(item, container)
|
||||
|
||||
# --- Snippet/highlight helpers -----------------------------------------
|
||||
def _make_html_snippet(
|
||||
self, markdown_src: str, query: str, *, radius=60, maxlen=180
|
||||
):
|
||||
def _make_html_snippet(self, markdown_src: str, query: str, radius=60, maxlen=180):
|
||||
# For markdown, we can work directly with the text
|
||||
# Strip markdown formatting for display
|
||||
plain = self._strip_markdown(markdown_src)
|
||||
|
|
@ -192,7 +177,7 @@ class Search(QWidget):
|
|||
lambda m: f"<b>{m.group(0)}</b>", snippet_html
|
||||
)
|
||||
|
||||
return snippet_html, start > 0, end < L
|
||||
return snippet_html
|
||||
|
||||
def _strip_markdown(self, markdown: str) -> str:
|
||||
"""Strip markdown formatting for plain text display."""
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ def test_make_html_snippet_and_strip_markdown(qtbot, fresh_db):
|
|||
long = (
|
||||
"This is **bold** text with alpha in the middle and some more trailing content."
|
||||
)
|
||||
frag, left, right = s._make_html_snippet(long, "alpha", radius=10, maxlen=40)
|
||||
frag = s._make_html_snippet(long, "alpha", radius=10, maxlen=40)
|
||||
assert "alpha" in frag
|
||||
s._strip_markdown("**bold** _italic_ ~~strike~~ 1. item - [x] check")
|
||||
|
||||
|
|
@ -70,12 +70,12 @@ def test_make_html_snippet_variants(qtbot, fresh_db):
|
|||
|
||||
# Case: query tokens not found -> idx < 0 path; expect right ellipsis when longer than maxlen
|
||||
src = " ".join(["word"] * 200)
|
||||
frag, left, right = s._make_html_snippet(src, "nomatch", radius=3, maxlen=30)
|
||||
assert frag and not left and right
|
||||
frag = s._make_html_snippet(src, "nomatch", radius=3, maxlen=30)
|
||||
assert frag
|
||||
|
||||
# Case: multiple tokens highlighted
|
||||
src = "Alpha bravo charlie delta echo"
|
||||
frag, left, right = s._make_html_snippet(src, "alpha delta", radius=2, maxlen=50)
|
||||
frag = s._make_html_snippet(src, "alpha delta", radius=2, maxlen=50)
|
||||
assert "<b>Alpha</b>" in frag or "<b>alpha</b>" in frag
|
||||
assert "<b>delta</b>" in frag
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue