mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-09-04 02:01:48 +03:00
fix(channels): preserve indentation across message splits
Signed-off-by: PP1 <74917296+pengpengyi92@users.noreply.github.com>
This commit is contained in:
@@ -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**.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user