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:
|
if not text_styles:
|
||||||
return [[] for _ in chunks]
|
return [[] for _ in chunks]
|
||||||
|
|
||||||
# Locate each chunk's UTF-16 start in plain_text. split_message lstrips at
|
# Locate each chunk in the original text. This accounts for delimiters
|
||||||
# boundaries (but not before the first chunk), so we skip whitespace
|
# removed at split points while retaining indentation inside a chunk.
|
||||||
# between chunks to mirror that.
|
|
||||||
chunk_ranges: list[tuple[int, int]] = []
|
chunk_ranges: list[tuple[int, int]] = []
|
||||||
cursor = 0 # Python codepoint cursor in plain_text
|
cursor = 0 # Python codepoint cursor in plain_text
|
||||||
for i, chunk in enumerate(chunks):
|
for chunk in chunks:
|
||||||
if i > 0:
|
chunk_start = plain_text.find(chunk, cursor)
|
||||||
while cursor < len(plain_text) and plain_text[cursor].isspace():
|
if chunk_start < 0:
|
||||||
cursor += 1
|
chunk_start = cursor
|
||||||
utf16_start = _utf16_len(plain_text[:cursor])
|
utf16_start = _utf16_len(plain_text[:chunk_start])
|
||||||
utf16_end = utf16_start + _utf16_len(chunk)
|
utf16_end = utf16_start + _utf16_len(chunk)
|
||||||
chunk_ranges.append((utf16_start, utf16_end))
|
chunk_ranges.append((utf16_start, utf16_end))
|
||||||
cursor += len(chunk)
|
cursor = chunk_start + len(chunk)
|
||||||
|
|
||||||
result: list[list[str]] = [[] for _ in chunks]
|
result: list[list[str]] = [[] for _ in chunks]
|
||||||
for entry in text_styles:
|
for entry in text_styles:
|
||||||
|
|||||||
@@ -384,6 +384,15 @@ def test_partition_styles_drops_styles_outside_chunks():
|
|||||||
assert parts == [[], []]
|
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():
|
def test_partition_styles_long_message_preserves_chunk_one_styles():
|
||||||
"""A bold span deep in the message must follow the message into chunk 1."""
|
"""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**.
|
# 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)
|
chunks.append(content)
|
||||||
break
|
break
|
||||||
cut = content[:max_len]
|
cut = content[:max_len]
|
||||||
# Try to break at newline first, then space, then hard break
|
# Consume only the newline itself so indentation starts the next chunk.
|
||||||
pos = cut.rfind("\n")
|
newline_pos = cut.rfind("\n")
|
||||||
if pos <= 0:
|
if newline_pos > 0:
|
||||||
pos = cut.rfind(" ")
|
chunks.append(content[:newline_pos])
|
||||||
if pos <= 0:
|
content = content[newline_pos + 1 :]
|
||||||
pos = max_len
|
continue
|
||||||
chunks.append(content[:pos])
|
|
||||||
content = content[pos:].lstrip()
|
# 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
|
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"]
|
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():
|
def test_split_message_nonpositive_maxlen_returns_unsplit():
|
||||||
content = "alpha beta gamma delta"
|
content = "alpha beta gamma delta"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user