Improve nested lists

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
jpirnay
2026-04-23 22:04:19 +02:00
co-authored by Copilot
parent e0c42278ad
commit e968a54e77
3 changed files with 46 additions and 5 deletions
+12 -2
View File
@@ -19,6 +19,15 @@ static std::string trimLeft(const std::string& s) {
return s.substr(i);
}
static uint8_t parseListIndentLevel(size_t leadingSpaces) {
// Top-level list markers may be preceded by up to 3 spaces.
// Nested list items require at least 4 spaces before the marker.
if (leadingSpaces < TAB_WIDTH) {
return 0;
}
return static_cast<uint8_t>((leadingSpaces - TAB_WIDTH) / TAB_WIDTH + 1);
}
static bool isWordChar(char c) { return std::isalnum(static_cast<unsigned char>(c)) || c == '_'; }
static bool isUnderscoreEmphasis(const std::string& text, size_t pos, size_t count) {
@@ -249,7 +258,8 @@ ParsedLine parseLine(const std::string& rawLine, bool inCodeBlock) {
return result;
}
// Count leading whitespace for nesting level before trimming
// Count leading whitespace for nesting level before trimming.
// Up to 3 spaces before a list marker are still top-level in CommonMark.
size_t leadingSpaces = 0;
for (size_t i = 0; i < rawLine.size(); i++) {
if (rawLine[i] == ' ')
@@ -259,7 +269,7 @@ ParsedLine parseLine(const std::string& rawLine, bool inCodeBlock) {
else
break;
}
result.indentLevel = static_cast<uint8_t>(leadingSpaces / TAB_WIDTH);
result.indentLevel = parseListIndentLevel(leadingSpaces);
std::string trimmed = trimLeft(rawLine);
+12 -3
View File
@@ -335,13 +335,18 @@ bool MdReaderActivity::wordWrapParsedLine(const MdParser::ParsedLine& parsed, in
const int availableWidth = viewportWidth - indent;
if (availableWidth <= 0) return true;
// Build a flat list of all spans, prepending the list prefix if present
// Build a flat list of all spans, prepending the list prefix if present.
std::vector<MdParser::Span> allSpans;
if (!parsed.listPrefix.empty()) {
allSpans.push_back({parsed.listPrefix, EpdFontFamily::REGULAR});
}
allSpans.insert(allSpans.end(), parsed.spans.begin(), parsed.spans.end());
const int listPrefixIndent =
!parsed.listPrefix.empty()
? renderer.getTextAdvanceX(cachedFontId, parsed.listPrefix.c_str(), EpdFontFamily::REGULAR)
: 0;
// Check if everything fits on one line
int totalWidth = measureSpans(allSpans);
if (totalWidth <= availableWidth) {
@@ -359,6 +364,7 @@ bool MdReaderActivity::wordWrapParsedLine(const MdParser::ParsedLine& parsed, in
currentLine.isCodeBlock = isCodeBlock;
int currentWidth = 0;
bool fullyConsumed = true;
bool firstLine = true;
for (size_t si = 0; si < allSpans.size(); si++) {
const auto& span = allSpans[si];
@@ -415,8 +421,10 @@ bool MdReaderActivity::wordWrapParsedLine(const MdParser::ParsedLine& parsed, in
}
} else {
outLines.push_back(std::move(currentLine));
firstLine = false;
currentLine = RenderedLine();
currentLine.indent = indent;
currentLine.indent = indent + (firstLine ? 0 : listPrefixIndent);
currentLine.isCodeBlock = isCodeBlock;
currentWidth = 0;
continue;
}
@@ -424,8 +432,9 @@ bool MdReaderActivity::wordWrapParsedLine(const MdParser::ParsedLine& parsed, in
currentLine.spans.push_back({remaining.substr(0, breakPos), style});
outLines.push_back(std::move(currentLine));
firstLine = false;
currentLine = RenderedLine();
currentLine.indent = indent;
currentLine.indent = indent + (firstLine ? 0 : listPrefixIndent);
currentLine.isCodeBlock = isCodeBlock;
currentWidth = 0;
+22
View File
@@ -78,6 +78,26 @@ void testUnderscoreBoldWorks() {
PASS();
}
void testNestedUnorderedListIndentLevel() {
printf("testNestedUnorderedListIndentLevel...\n");
auto parsed = MdParser::parseLine(" - nested item", false);
ASSERT_EQ(parsed.blockType, MdParser::BlockType::UnorderedList);
ASSERT_EQ(parsed.listPrefix, "\xe2\x80\xa2 ");
ASSERT_EQ(parsed.indentLevel, 1);
ASSERT_EQ(flattenText(parsed.spans), "nested item");
PASS();
}
void testNestedOrderedListIndentLevel() {
printf("testNestedOrderedListIndentLevel...\n");
auto parsed = MdParser::parseLine(" 1. nested ordered", false);
ASSERT_EQ(parsed.blockType, MdParser::BlockType::OrderedList);
ASSERT_EQ(parsed.listPrefix, "1. ");
ASSERT_EQ(parsed.indentLevel, 2);
ASSERT_EQ(flattenText(parsed.spans), "nested ordered");
PASS();
}
int main() {
printf("=== Markdown Parser Tests ===\n\n");
@@ -86,6 +106,8 @@ int main() {
testUnderscoreEmphasisStillWorks();
testAsteriskEmphasisStillWorks();
testUnderscoreBoldWorks();
testNestedUnorderedListIndentLevel();
testNestedOrderedListIndentLevel();
printf("\n=== Results: %d passed, %d failed ===\n", testsPassed, testsFailed);
return testsFailed > 0 ? 1 : 0;