diff --git a/src/settings.ts b/src/settings.ts index 6c89a10..be04467 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -20,8 +20,8 @@ const DEFAULT_SETTINGS: ExtendedTaskListsSettings = { useHierarchy: false, excludeFilePattern: "", excludeFolderFilename: ".exclude_todos", - excludeRegionBegin: "%% exclude: start %%", - excludeRegionEnd: "%% exclude: end %%", + excludeRegionBegin: "", + excludeRegionEnd: "", includeNotStarted: true, includeInProgress: true, includeWontDo: false, @@ -88,29 +88,29 @@ class ExtendedTaskListsSettingTab extends PluginSettingTab { new Setting(containerEl).setName("Excludes").setHeading(); new Setting(containerEl) - .setName("Exclude file pattern") + .setName("Exclude folder filename") .setDesc( - "A pattern that should be inserted anywhere in a Markdown file to exclude it from the generated TODO file.", + 'The filename to add to a folder to exclude all task lists in it from the generated TODO file. You may prefer to change the default value since dot files (files that start with a ".") do not show up within Obsidian.', ) .addText((text) => text - .setValue(this.plugin.settings.excludeFilePattern) + .setValue(this.plugin.settings.excludeFolderFilename) .onChange(async (value) => { - this.plugin.settings.excludeFilePattern = value; + this.plugin.settings.excludeFolderFilename = value; await this.plugin.saveSettings(); }), ); new Setting(containerEl) - .setName("Exclude folder filename") + .setName("Exclude file pattern") .setDesc( - 'The filename to add to a folder to exclude all task lists in it from the generated TODO file. You may prefer to change the default value since dot files (files that start with a ".") do not show up within Obsidian.', + "A pattern that should be inserted anywhere in a Markdown file to exclude it from the generated TODO file.", ) .addText((text) => text - .setValue(this.plugin.settings.excludeFolderFilename) + .setValue(this.plugin.settings.excludeFilePattern) .onChange(async (value) => { - this.plugin.settings.excludeFolderFilename = value; + this.plugin.settings.excludeFilePattern = value; await this.plugin.saveSettings(); }), ); diff --git a/src/todoService.test.ts b/src/todoService.test.ts index 27021f8..e5fe0ef 100644 --- a/src/todoService.test.ts +++ b/src/todoService.test.ts @@ -99,6 +99,27 @@ class MockFileService implements IFileService { } describe("TodoService", () => { + test("findTodosFiles excludes files with alternate comment style", async () => { + // Arrange + const taskFile = createMockFile("test.md", "- [ ] task item"); + const fileWithObsidianExclude = createMockFile( + "Exclude.md", + "%% exclude TODO %%", + ); + + const files = [taskFile, fileWithObsidianExclude]; + const mockFileService = new MockFileService(files); + + // Act + const todoService = new TodoService(mockFileService, MOCK_SETTINGS); + const actual = await todoService.findTodosFiles(); + + // Assert + expect(actual).toEqual([ + { file: taskFile, contents: "- [ ] task item" } as TodoFile, + ]); + }); + test("findTodosFiles excludes files", async () => { // Arrange const taskFile = createMockFile("test.md", "- [ ] task item"); @@ -383,6 +404,38 @@ describe("TodoService", () => { expect(actual).toEqual(expected); }); + test("parseTodos excludes task items inside HTML-style exclude regions", () => { + // Arrange + const content = `- [ ] Included + +- [ ] Excluded +- [.] Also excluded + +- [ ] Also included`; + + const mockFileService = new MockFileService([]); + + // Act + const todoService = new TodoService(mockFileService, MOCK_SETTINGS); + const actual = todoService.parseTodos(content); + + // Assert + expect(actual).toEqual([ + { + task: TaskType.NotStarted, + text: "Included", + indentation: "", + lineno: 0, + } as Todo, + { + task: TaskType.NotStarted, + text: "Also included", + indentation: "", + lineno: 5, + } as Todo, + ]); + }); + test("parseTodos excludes task items inside exclude regions", () => { // Arrange const content = `- [ ] Included diff --git a/src/todoService.ts b/src/todoService.ts index a361986..748f0c6 100644 --- a/src/todoService.ts +++ b/src/todoService.ts @@ -33,6 +33,24 @@ interface IndexMatch { const TODO_PATTERN = /^(?\s*)-\s?\[(?.)\]\s+(?.*)$/; const LINK_PATTERN = /^- \[.*\]\((?.*)\)$/; +function alternateCommentStyle(pattern: string): string | null { + const obsidianMatch = pattern.match(/^%%\s*(.*?)\s*%%$/); + if (obsidianMatch) { + return ``; + } + const htmlMatch = pattern.match(/^$/); + if (htmlMatch) { + return `%% ${htmlMatch[1]} %%`; + } + return null; +} + +function matchesEitherCommentStyle(line: string, pattern: string): boolean { + if (line === pattern) return true; + const alt = alternateCommentStyle(pattern); + return alt != null && line === alt; +} + class TodoService { private fileService: IFileService; private settings: ExtendedTaskListsSettings; @@ -72,9 +90,11 @@ class TodoService { (todoFile) => !todoFile.contents .split(/[\r\n]+/) - .some( - (line) => - line.trim() === this.settings.excludeFilePattern, + .some((line) => + matchesEitherCommentStyle( + line.trim(), + this.settings.excludeFilePattern, + ), ), ); return todoFiles; @@ -119,11 +139,17 @@ class TodoService { let inExcludedRegion = false; const matchesAndIndices = lines .map((line, index) => { - if (beginPattern && line.trim() === beginPattern) { + if ( + beginPattern && + matchesEitherCommentStyle(line.trim(), beginPattern) + ) { inExcludedRegion = true; } if (inExcludedRegion) { - if (endPattern && line.trim() === endPattern) { + if ( + endPattern && + matchesEitherCommentStyle(line.trim(), endPattern) + ) { inExcludedRegion = false; } return {