Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ export function parseTreeContent(content: string): ParsedTree {
lines = lines.slice(1);
}

// Detect the indentation mode (2-space or 4-space) for the entire content
const indentSize = detectIndentSize(lines);

const nodes: TreeNode[] = [];
const stack: { node: TreeNode; indent: number }[] = [];

for (const line of lines) {
const indent = calculateIndent(line);
const indent = calculateIndent(line, indentSize);
const fullName = extractName(line);

// Extract name and comment
Expand Down Expand Up @@ -62,44 +65,74 @@ export function parseTreeContent(content: string): ParsedTree {
return { nodes, raw: content };
}

/**
* Detect the indentation size used in the content
* Returns 2 for 2-space indentation, 4 for 4-space indentation
*/
function detectIndentSize(lines: string[]): number {
for (const line of lines) {
// Look for space-only indentation before branch characters
const match = line.match(/^( +)[├└]/);
if (match) {
const spaceCount = match[1].length;
// If we find a line with 2 spaces (not 4), it's 2-space mode
if (spaceCount === 2) {
return 2;
}
// If we find exactly 4 spaces, check if there are any 2-space lines
// to distinguish between "4-space mode" and "2-space mode at level 2"
}

// Also check for "│ " (pipe + 1 space) pattern which indicates 2-space mode
if (/│ [├└]/.test(line)) {
return 2;
}
}

// Default to 4-space mode
return 4;
}

/**
* Calculate indent level from line
* Supports both 4-character ("│ ") and 2-character ("│ ") indent patterns
*
* @param line - The line to calculate indent for
* @param indentSize - The detected indent size (2 or 4)
*/
function calculateIndent(line: string): number {
function calculateIndent(line: string, indentSize: number): number {
let indent = 0;
let i = 0;

while (i < line.length) {
const char = line[i];

// Check for "│ " pattern (vertical line + 3 spaces) - 4-char indent
if (char === '│' && line.substring(i, i + 4) === '│ ') {
if (indentSize === 4 && char === '│' && line.substring(i, i + 4) === '│ ') {
indent++;
i += 4;
continue;
}

// Check for "│ " pattern (vertical line + 1 space) - 2-char indent
// Must check this AFTER 4-char to avoid partial matches
if (char === '│' && line[i + 1] === ' ') {
if (indentSize === 2 && char === '│' && line[i + 1] === ' ') {
indent++;
i += 2;
continue;
}

// Check for 4 spaces
if (line.substring(i, i + 4) === ' ') {
indent++;
i += 4;
continue;
}

// Check for 2 spaces (must come after 4-space check)
if (line.substring(i, i + 2) === ' ') {
indent++;
i += 2;
continue;
// Handle space-only indentation based on detected indent size
if (char === ' ') {
if (indentSize === 2 && line.substring(i, i + 2) === ' ') {
indent++;
i += 2;
continue;
}
if (indentSize === 4 && line.substring(i, i + 4) === ' ') {
indent++;
i += 4;
continue;
}
}

// Check for branch characters (├── or └──)
Expand Down
63 changes: 63 additions & 0 deletions packages/rspress-plugin-file-tree/tests/parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -897,3 +897,66 @@ doc_build
expect(ellipsis2.name).toBe('...');
expect(ellipsis2.type).toBe('file');
});

test('Should parse docs structure with basic directory containing mdx files', () => {
const input = `
docs
├── _meta.json
└── guides
├── _meta.json
└── basic
├── introduction.mdx
├── install.mdx
└── plugin-development.md
`;

const result = parseTreeContent(input).nodes;

// docs is the root directory
const docs = result[0];
expect(docs.name).toBe('docs');
expect(docs.type).toBe('directory');
expect(docs.children.length).toBe(2);

// _meta.json at root level
const metaJson = docs.children[0];
expect(metaJson.name).toBe('_meta.json');
expect(metaJson.type).toBe('file');
expect(metaJson.extension).toBe('json');

// guides directory
const guides = docs.children[1];
expect(guides.name).toBe('guides');
expect(guides.type).toBe('directory');
expect(guides.children.length).toBe(2);

// _meta.json in guides
const guidesMetaJson = guides.children[0];
expect(guidesMetaJson.name).toBe('_meta.json');
expect(guidesMetaJson.type).toBe('file');
expect(guidesMetaJson.extension).toBe('json');

// basic directory (should be a directory, not a file)
const basic = guides.children[1];
expect(basic.name).toBe('basic');
expect(basic.type).toBe('directory');
expect(basic.children.length).toBe(3);

// introduction.mdx
const introduction = basic.children[0];
expect(introduction.name).toBe('introduction.mdx');
expect(introduction.type).toBe('file');
expect(introduction.extension).toBe('mdx');

// install.mdx
const install = basic.children[1];
expect(install.name).toBe('install.mdx');
expect(install.type).toBe('file');
expect(install.extension).toBe('mdx');

// plugin-development.md
const pluginDev = basic.children[2];
expect(pluginDev.name).toBe('plugin-development.md');
expect(pluginDev.type).toBe('file');
expect(pluginDev.extension).toBe('md');
});