+12
-2
@@ -19,6 +19,15 @@ static std::string trimLeft(const std::string& s) {
|
|||||||
return s.substr(i);
|
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 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) {
|
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;
|
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;
|
size_t leadingSpaces = 0;
|
||||||
for (size_t i = 0; i < rawLine.size(); i++) {
|
for (size_t i = 0; i < rawLine.size(); i++) {
|
||||||
if (rawLine[i] == ' ')
|
if (rawLine[i] == ' ')
|
||||||
@@ -259,7 +269,7 @@ ParsedLine parseLine(const std::string& rawLine, bool inCodeBlock) {
|
|||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
result.indentLevel = static_cast<uint8_t>(leadingSpaces / TAB_WIDTH);
|
result.indentLevel = parseListIndentLevel(leadingSpaces);
|
||||||
|
|
||||||
std::string trimmed = trimLeft(rawLine);
|
std::string trimmed = trimLeft(rawLine);
|
||||||
|
|
||||||
|
|||||||
@@ -335,13 +335,18 @@ bool MdReaderActivity::wordWrapParsedLine(const MdParser::ParsedLine& parsed, in
|
|||||||
const int availableWidth = viewportWidth - indent;
|
const int availableWidth = viewportWidth - indent;
|
||||||
if (availableWidth <= 0) return true;
|
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;
|
std::vector<MdParser::Span> allSpans;
|
||||||
if (!parsed.listPrefix.empty()) {
|
if (!parsed.listPrefix.empty()) {
|
||||||
allSpans.push_back({parsed.listPrefix, EpdFontFamily::REGULAR});
|
allSpans.push_back({parsed.listPrefix, EpdFontFamily::REGULAR});
|
||||||
}
|
}
|
||||||
allSpans.insert(allSpans.end(), parsed.spans.begin(), parsed.spans.end());
|
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
|
// Check if everything fits on one line
|
||||||
int totalWidth = measureSpans(allSpans);
|
int totalWidth = measureSpans(allSpans);
|
||||||
if (totalWidth <= availableWidth) {
|
if (totalWidth <= availableWidth) {
|
||||||
@@ -359,6 +364,7 @@ bool MdReaderActivity::wordWrapParsedLine(const MdParser::ParsedLine& parsed, in
|
|||||||
currentLine.isCodeBlock = isCodeBlock;
|
currentLine.isCodeBlock = isCodeBlock;
|
||||||
int currentWidth = 0;
|
int currentWidth = 0;
|
||||||
bool fullyConsumed = true;
|
bool fullyConsumed = true;
|
||||||
|
bool firstLine = true;
|
||||||
|
|
||||||
for (size_t si = 0; si < allSpans.size(); si++) {
|
for (size_t si = 0; si < allSpans.size(); si++) {
|
||||||
const auto& span = allSpans[si];
|
const auto& span = allSpans[si];
|
||||||
@@ -415,8 +421,10 @@ bool MdReaderActivity::wordWrapParsedLine(const MdParser::ParsedLine& parsed, in
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
outLines.push_back(std::move(currentLine));
|
outLines.push_back(std::move(currentLine));
|
||||||
|
firstLine = false;
|
||||||
currentLine = RenderedLine();
|
currentLine = RenderedLine();
|
||||||
currentLine.indent = indent;
|
currentLine.indent = indent + (firstLine ? 0 : listPrefixIndent);
|
||||||
|
currentLine.isCodeBlock = isCodeBlock;
|
||||||
currentWidth = 0;
|
currentWidth = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -424,8 +432,9 @@ bool MdReaderActivity::wordWrapParsedLine(const MdParser::ParsedLine& parsed, in
|
|||||||
|
|
||||||
currentLine.spans.push_back({remaining.substr(0, breakPos), style});
|
currentLine.spans.push_back({remaining.substr(0, breakPos), style});
|
||||||
outLines.push_back(std::move(currentLine));
|
outLines.push_back(std::move(currentLine));
|
||||||
|
firstLine = false;
|
||||||
currentLine = RenderedLine();
|
currentLine = RenderedLine();
|
||||||
currentLine.indent = indent;
|
currentLine.indent = indent + (firstLine ? 0 : listPrefixIndent);
|
||||||
currentLine.isCodeBlock = isCodeBlock;
|
currentLine.isCodeBlock = isCodeBlock;
|
||||||
currentWidth = 0;
|
currentWidth = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,26 @@ void testUnderscoreBoldWorks() {
|
|||||||
PASS();
|
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() {
|
int main() {
|
||||||
printf("=== Markdown Parser Tests ===\n\n");
|
printf("=== Markdown Parser Tests ===\n\n");
|
||||||
|
|
||||||
@@ -86,6 +106,8 @@ int main() {
|
|||||||
testUnderscoreEmphasisStillWorks();
|
testUnderscoreEmphasisStillWorks();
|
||||||
testAsteriskEmphasisStillWorks();
|
testAsteriskEmphasisStillWorks();
|
||||||
testUnderscoreBoldWorks();
|
testUnderscoreBoldWorks();
|
||||||
|
testNestedUnorderedListIndentLevel();
|
||||||
|
testNestedOrderedListIndentLevel();
|
||||||
|
|
||||||
printf("\n=== Results: %d passed, %d failed ===\n", testsPassed, testsFailed);
|
printf("\n=== Results: %d passed, %d failed ===\n", testsPassed, testsFailed);
|
||||||
return testsFailed > 0 ? 1 : 0;
|
return testsFailed > 0 ? 1 : 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user