diff --git a/extensions/zalouser/src/text-styles.test.ts b/extensions/zalouser/src/text-styles.test.ts index c8f5f50bd3d..b764a88ae2c 100644 --- a/extensions/zalouser/src/text-styles.test.ts +++ b/extensions/zalouser/src/text-styles.test.ts @@ -97,6 +97,13 @@ describe("parseZalouserTextStyles", () => { }); }); + it("treats spaced nested blockquotes as deeper quoted lines", () => { + expect(parseZalouserTextStyles("> > quoted")).toEqual({ + text: "quoted", + styles: [{ start: 0, len: 6, st: TextStyle.Indent, indentSize: 2 }], + }); + }); + it("treats indented quoted fences as literal code blocks", () => { expect(parseZalouserTextStyles(" > ```\n > *cmd*\n > ```")).toEqual({ text: "*cmd*", @@ -104,6 +111,13 @@ describe("parseZalouserTextStyles", () => { }); }); + it("treats spaced nested quoted fences as literal code blocks", () => { + expect(parseZalouserTextStyles("> > ```\n> > code\n> > ```")).toEqual({ + text: "code", + styles: [], + }); + }); + it("preserves inner quote markers inside quoted fenced code blocks", () => { expect(parseZalouserTextStyles("> ```\n>> prompt\n> ```")).toEqual({ text: "> prompt", @@ -111,6 +125,17 @@ describe("parseZalouserTextStyles", () => { }); }); + it("keeps quote indentation on heading lines", () => { + expect(parseZalouserTextStyles("> # Title")).toEqual({ + text: "Title", + styles: [ + { start: 0, len: 5, st: TextStyle.Bold }, + { start: 0, len: 5, st: TextStyle.Big }, + { start: 0, len: 5, st: TextStyle.Indent, indentSize: 1 }, + ], + }); + }); + it("keeps unmatched fences literal", () => { expect(parseZalouserTextStyles("```python")).toEqual({ text: "```python", @@ -150,4 +175,11 @@ describe("parseZalouserTextStyles", () => { styles: [], }); }); + + it("keeps indented code blocks literal", () => { + expect(parseZalouserTextStyles(" *cmd*")).toEqual({ + text: "\u00A0\u00A0\u00A0\u00A0*cmd*", + styles: [], + }); + }); }); diff --git a/extensions/zalouser/src/text-styles.ts b/extensions/zalouser/src/text-styles.ts index 91d93b2e6f8..d51c915d816 100644 --- a/extensions/zalouser/src/text-styles.ts +++ b/extensions/zalouser/src/text-styles.ts @@ -143,14 +143,33 @@ export function parseZalouserTextStyles(input: string): { text: string; styles: continue; } + const outputLineIndex = processedLines.length; + if (isIndentedCodeBlockLine(line)) { + if (baseIndent > 0) { + lineStyles.push({ + lineIndex: outputLineIndex, + style: TextStyle.Indent, + indentSize: baseIndent, + }); + } + processedLines.push(escapeLiteralText(normalizeCodeBlockLeadingWhitespace(line), escapeMap)); + continue; + } + const headingMatch = line.match(/^(#{1,4})\s(.*)$/); if (headingMatch) { - const outputLineIndex = processedLines.length; const depth = headingMatch[1].length; lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.Bold }); if (depth === 1) { lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.Big }); } + if (baseIndent > 0) { + lineStyles.push({ + lineIndex: outputLineIndex, + style: TextStyle.Indent, + indentSize: baseIndent, + }); + } processedLines.push(headingMatch[2]); continue; } @@ -163,7 +182,6 @@ export function parseZalouserTextStyles(input: string): { text: string; styles: content = indentMatch[2]; } const totalIndent = Math.min(5, baseIndent + indentLevel); - const outputLineIndex = processedLines.length; if (/^[-*+]\s\[[ xX]\]\s/.test(content)) { if (totalIndent > 0) { @@ -331,18 +349,27 @@ function stripQuotePrefix( line: string, maxDepth = Number.POSITIVE_INFINITY, ): { text: string; indent: number } { - const match = line.match(/^([ ]{0,3})(>+)( ?)(.*)$/); - if (!match) { + let cursor = 0; + while (cursor < line.length && cursor < 3 && line[cursor] === " ") { + cursor += 1; + } + + let removedDepth = 0; + let consumedCursor = cursor; + while (removedDepth < maxDepth && consumedCursor < line.length && line[consumedCursor] === ">") { + removedDepth += 1; + consumedCursor += 1; + if (line[consumedCursor] === " ") { + consumedCursor += 1; + } + } + + if (removedDepth === 0) { return { text: line, indent: 0 }; } - const removedDepth = Math.min(match[2].length, maxDepth); - const remainingMarkers = match[2].slice(removedDepth); - const remainingText = - remainingMarkers.length > 0 ? `${remainingMarkers}${match[3]}${match[4]}` : match[4]; - return { - text: remainingText, + text: line.slice(consumedCursor), indent: Math.min(5, removedDepth), }; } @@ -468,6 +495,10 @@ function normalizeCodeBlockLeadingWhitespace(line: string): string { ); } +function isIndentedCodeBlockLine(line: string): boolean { + return /^(?: {4,}|\t)/.test(line); +} + function stripCodeFenceIndent(line: string, indent: number): string { let consumed = 0; let cursor = 0;