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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

To see tags and releases, please go to [Tags](https://github.com/AlexJSully/workspace-wiki/tags) on [GitHub](https://github.com/AlexJSully/workspace-wiki).

## [1.0.4] - 2025-10-30

Bug Fix:

- Fixed a bug where README files with no extension were not appearing in the Workspace Wiki tree even when Markdown was a supported extension.

## [1.0.3] - 2025-10-28

Features:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ This extension contributes the following settings under the `workspaceWiki` name

Array of file extensions to include in the workspace wiki.

**Special Case:** If `md` or `markdown` is included, files named `README` (with no extension, case-insensitive) are also included and treated as Markdown.

```json
{
"workspaceWiki.supportedExtensions": ["md", "markdown", "txt", "html", "pdf"]
Expand Down
13 changes: 9 additions & 4 deletions docs/architecture/scanner.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The Scanner/Indexer is implemented in [`src/scanner/workspaceScanner.ts`](../../
## How It Works

- Uses `workspace.findFiles` to locate files matching supported extensions (e.g., `.md`, `.markdown`, `.txt`).
- If Markdown is a supported extension, also scans for files named `README` (with no extension, case-insensitive) and treats them as Markdown.
- Applies exclude patterns from settings and `.gitignore`.
- Filters hidden files/folders (starting with dot) based on `showHiddenFiles` setting.
- Filters ignored files based on `showIgnoredFiles` setting and exclude patterns.
Expand All @@ -18,10 +19,11 @@ The Scanner/Indexer is implemented in [`src/scanner/workspaceScanner.ts`](../../
## File Filtering Logic

1. **Extension Matching**: Only includes files with supported extensions
2. **Exclude Pattern Filtering**: Applies `excludeGlobs` and `.gitignore` patterns
3. **Hidden File Filtering**: Excludes files/folders starting with `.` unless `showHiddenFiles` is true
4. **Ignored File Filtering**: Excludes files in `.gitignore` unless `showIgnoredFiles` is true
5. **Depth Limiting**: Respects `maxSearchDepth` setting by calculating depth relative to workspace root
2. **README (no extension) Matching**: If Markdown is supported, also includes files named `README` (no extension, case-insensitive) as Markdown
3. **Exclude Pattern Filtering**: Applies `excludeGlobs` and `.gitignore` patterns
4. **Hidden File Filtering**: Excludes files/folders starting with `.` unless `showHiddenFiles` is true
5. **Ignored File Filtering**: Excludes files in `.gitignore` unless `showIgnoredFiles` is true
6. **Depth Limiting**: Respects `maxSearchDepth` setting by calculating depth relative to workspace root

## Depth Calculation

Expand All @@ -36,7 +38,10 @@ The scanner calculates file depth relative to the workspace root for the `maxSea
## Example

```ts
// Standard scan for supported extensions
const files = await vscode.workspace.findFiles('**/*.{md,markdown,txt}', '**/node_modules/**');
// Additionally, if Markdown is supported, scan for README (no extension)
const readmes = await vscode.workspace.findFiles('**/README', '**/node_modules/**');
```

## Edge Cases
Expand Down
1 change: 1 addition & 0 deletions docs/architecture/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The Settings Manager reads and applies user configuration for the Workspace Wiki
### File Discovery & Filtering

- `workspaceWiki.supportedExtensions`: File types to scan (default: `md`, `markdown`, `txt`).
- If `md` or `markdown` is included, files named `README` (no extension, case-insensitive) are also included and treated as Markdown.
- `workspaceWiki.excludeGlobs`: Patterns to exclude (e.g., `**/node_modules/**`).
- `workspaceWiki.maxSearchDepth`: Limit scan depth for large repos.
- `workspaceWiki.showIgnoredFiles`: Show files listed in .gitignore and excludeGlobs (default: false).
Expand Down
7 changes: 7 additions & 0 deletions example/file-types-test/test-proto.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
syntax = "proto3";

// test-proto.proto for file type support testing
message TestProto {
string id = 1;
string name = 2;
}
5 changes: 5 additions & 0 deletions example/nested-structure-test/subdirectory-3/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# README

This is a README file with no extension, for testing Workspace Wiki support for extension-less Markdown files.

It should be detected and treated as a Markdown file if Markdown is supported in the extension settings.
70 changes: 48 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "workspace-wiki",
"displayName": "Workspace Wiki",
"description": "Workspace Wiki",
"version": "1.0.3",
"version": "1.0.4",
"publisher": "alexjsully",
"engines": {
"vscode": "^1.99.3"
Expand Down
61 changes: 61 additions & 0 deletions src/scanner/workspaceScanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,67 @@ describe('workspaceScanner', () => {
});

describe('Supported Extensions', () => {
it('should include README (no extension) if Markdown is supported', async () => {
let calledPatterns: string[] = [];
const mockWorkspace: WorkspaceLike = {
findFiles: async (pattern: string) => {
calledPatterns.push(pattern);
// Simulate README (no extension) file
if (pattern === '**/README' || pattern === '**/readme') {
return [
{ fsPath: '/project-root/README' },
{ fsPath: '/project-root/docs/README' },
{ fsPath: '/project-root/docs/readme' },
];
}
return [];
},
getConfiguration: () => ({
get: (key: string) => {
if (key === 'supportedExtensions') {
return ['md', 'markdown', 'txt'];
}
return undefined;
},
}),
};

const result = await scanWorkspaceDocs(mockWorkspace);
// Should include all README (no extension) files
const readmeFiles = result.filter((uri: any) => /README$/i.test(uri.fsPath));
assert.ok(readmeFiles.length >= 3, 'Should detect README files with no extension');
// Should call the README patterns
assert.ok(calledPatterns.includes('**/README'));
assert.ok(calledPatterns.includes('**/readme'));
});

it('should NOT include README (no extension) if Markdown is NOT supported', async () => {
let calledPatterns: string[] = [];
const mockWorkspace: WorkspaceLike = {
findFiles: async (pattern: string) => {
calledPatterns.push(pattern);
if (pattern === '**/README' || pattern === '**/readme') {
// Should not be called
throw new Error('README pattern should not be called if Markdown is not supported');
}
return [];
},
getConfiguration: () => ({
get: (key: string) => {
if (key === 'supportedExtensions') {
return ['txt', 'html'];
}
return undefined;
},
}),
};

await scanWorkspaceDocs(mockWorkspace);
// Should NOT call the README patterns
assert.ok(!calledPatterns.includes('**/README'));
assert.ok(!calledPatterns.includes('**/readme'));
});

it('should scan default extensions (md, markdown, txt)', async () => {
const patterns: string[] = [];
const mockWorkspace: WorkspaceLike = {
Expand Down
26 changes: 25 additions & 1 deletion src/scanner/workspaceScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export async function scanWorkspaceDocs(workspace: WorkspaceLike): Promise<any[]
let maxSearchDepth = 10;
let showIgnoredFiles = false;
let showHiddenFiles = false;

if (workspace.getConfiguration) {
const config = workspace.getConfiguration('workspaceWiki');
supportedExtensions = config.get('supportedExtensions') || supportedExtensions;
Expand Down Expand Up @@ -71,7 +72,18 @@ export async function scanWorkspaceDocs(workspace: WorkspaceLike): Promise<any[]
supportedExtensions = ['md', 'markdown', 'txt']; // Fallback to defaults
}

const patterns = supportedExtensions.map((ext) => `**/*.${ext}`);
// Add README (no extension) support if Markdown is enabled
let patterns = supportedExtensions.map((ext) => `**/*.${ext}`);

const markdownExts = ['md', 'markdown'];
const hasMarkdown = supportedExtensions.some((ext) => markdownExts.includes(ext.toLowerCase()));

if (hasMarkdown) {
// README (no extension) at any depth, case-insensitive
patterns.push('**/README');
patterns.push('**/readme');
}

const exclude = !showIgnoredFiles && excludeGlobs.length > 0 ? `{${excludeGlobs.join(',')}}` : undefined;

const results: any[] = [];
Expand All @@ -80,6 +92,15 @@ export async function scanWorkspaceDocs(workspace: WorkspaceLike): Promise<any[]
if (!uris) {
uris = [];
}

// For README (no extension), filter to only files named exactly 'README' (case-insensitive, no extension)
if (pattern === '**/README' || pattern === '**/readme') {
uris = uris.filter((uri: any) => {
const fileName = uri.fsPath.split(/[\\/]/).pop() || '';
return /^readme$/i.test(fileName);
});
}

if (!showIgnoredFiles && excludeGlobs.length > 0) {
uris = uris.filter((uri: any) => {
const shouldExclude = excludeGlobs.some((glob) => {
Expand All @@ -90,12 +111,14 @@ export async function scanWorkspaceDocs(workspace: WorkspaceLike): Promise<any[]
return !shouldExclude;
});
}

if (!showHiddenFiles) {
uris = uris.filter((uri: any) => {
const segments = uri.fsPath.split(/[\\/]/);
return !segments.some((seg: string) => seg.startsWith('.') && seg.length > 1);
});
}

if (maxSearchDepth > 0) {
uris = uris.filter((uri: any) => {
const normalizedPath = uri.fsPath.replace(/\\/g, '/');
Expand Down Expand Up @@ -141,5 +164,6 @@ export async function scanWorkspaceDocs(workspace: WorkspaceLike): Promise<any[]
}
results.push(...uris);
}

return results;
}
Loading