fix(channels): preserve indentation across message splits

Signed-off-by: PP1 <74917296+pengpengyi92@users.noreply.github.com>
This commit is contained in:
PP1
2026-09-04 01:39:41 +08:00
committed by Xubin Ren
parent a8cbcc1c81
commit 01760b7385
4 changed files with 46 additions and 17 deletions
+8 -9
View File
@@ -269,19 +269,18 @@ def _partition_styles(
if not text_styles:
return [[] for _ in chunks]
# Locate each chunk's UTF-16 start in plain_text. split_message lstrips at
# boundaries (but not before the first chunk), so we skip whitespace
# between chunks to mirror that.
# Locate each chunk in the original text. This accounts for delimiters
# removed at split points while retaining indentation inside a chunk.
chunk_ranges: list[tuple[int, int]] = []
cursor = 0 # Python codepoint cursor in plain_text
for i, chunk in enumerate(chunks):
if i > 0:
while cursor < len(plain_text) and plain_text[cursor].isspace():
cursor += 1
utf16_start = _utf16_len(plain_text[:cursor])
for chunk in chunks:
chunk_start = plain_text.find(chunk, cursor)
if chunk_start < 0:
chunk_start = cursor
utf16_start = _utf16_len(plain_text[:chunk_start])
utf16_end = utf16_start + _utf16_len(chunk)
chunk_ranges.append((utf16_start, utf16_end))
cursor += len(chunk)
cursor = chunk_start + len(chunk)
result: list[list[str]] = [[] for _ in chunks]
for entry in text_styles:
@@ -384,6 +384,15 @@ def test_partition_styles_drops_styles_outside_chunks():
assert parts == [[], []]
def test_partition_styles_keeps_offset_in_indented_chunk():
"""Styles after preserved indentation remain relative to the chunk."""
plain = "header\n code"
chunks = split_message(plain, 10)
assert chunks == ["header", " code"]
assert _partition_styles(plain, chunks, ["11:4:BOLD"]) == [[], ["4:4:BOLD"]]
def test_partition_styles_long_message_preserves_chunk_one_styles():
"""A bold span deep in the message must follow the message into chunk 1."""
# Two ~30-char paragraphs separated by a blank line, then **tail**.
+17 -8
View File
@@ -639,14 +639,23 @@ def split_message(content: str, max_len: int = 2000) -> list[str]:
chunks.append(content)
break
cut = content[:max_len]
# Try to break at newline first, then space, then hard break
pos = cut.rfind("\n")
if pos <= 0:
pos = cut.rfind(" ")
if pos <= 0:
pos = max_len
chunks.append(content[:pos])
content = content[pos:].lstrip()
# Consume only the newline itself so indentation starts the next chunk.
newline_pos = cut.rfind("\n")
if newline_pos > 0:
chunks.append(content[:newline_pos])
content = content[newline_pos + 1 :]
continue
# Keep the existing word-boundary behavior, but avoid emitting a
# whitespace-only chunk when an indented line exceeds max_len.
space_pos = cut.rfind(" ")
if space_pos > 0 and cut[:space_pos].strip():
chunks.append(content[:space_pos])
content = content[space_pos:].lstrip()
continue
chunks.append(content[:max_len])
content = content[max_len:]
return chunks
+12
View File
@@ -17,6 +17,18 @@ def test_split_message_no_code_blocks_unchanged():
assert split_message(content, max_len=12) == ["alpha beta", "gamma delta"]
def test_split_message_preserves_indentation_after_newline():
content = "header\n indented code"
assert split_message(content, max_len=18) == ["header", " indented code"]
def test_split_message_preserves_indentation_across_hard_break():
content = "head\n abcdefghij"
assert split_message(content, max_len=8) == ["head", " abcd", "efghij"]
def test_split_message_nonpositive_maxlen_returns_unsplit():
content = "alpha beta gamma delta"