From 6d749cbd87ccffbffe777f90d8b7456d11a50ac2 Mon Sep 17 00:00:00 2001 From: joeriddles Date: Tue, 12 May 2026 22:54:00 -0700 Subject: [PATCH 1/3] feat: add exclude regions Signed-off-by: joeriddles --- src/settings.ts | 32 +++++++++++++++++ src/todoService.test.ts | 76 +++++++++++++++++++++++++++++++++++++++++ src/todoService.ts | 12 +++++++ 3 files changed, 120 insertions(+) diff --git a/src/settings.ts b/src/settings.ts index 86bae1c..93b546a 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -4,6 +4,8 @@ interface ExtendedTaskListsSettings { todoFilename: string; excludeFilePattern: string; excludeFolderFilename: string; + excludeRegionBegin: string; + excludeRegionEnd: string; useFullFilepath: boolean; includeNotStarted: boolean; includeInProgress: boolean; @@ -15,6 +17,8 @@ const DEFAULT_SETTINGS: ExtendedTaskListsSettings = { todoFilename: "TODO.md", excludeFilePattern: "", excludeFolderFilename: ".exclude_todos", + excludeRegionBegin: "%% exclude: start %%", + excludeRegionEnd: "%% exclude: end %%", useFullFilepath: false, includeNotStarted: true, includeInProgress: true, @@ -97,6 +101,34 @@ class ExtendedTaskListsSettingTab extends PluginSettingTab { new Setting(containerEl).setName("Includes").setHeading(); + new Setting(containerEl) + .setName("Exclude region begin") + .setDesc( + "A line matching this pattern marks the start of a region whose task items are excluded from the generated TODO file.", + ) + .addText((text) => + text + .setValue(this.plugin.settings.excludeRegionBegin) + .onChange(async (value) => { + this.plugin.settings.excludeRegionBegin = value; + await this.plugin.saveSettings(); + }), + ); + + new Setting(containerEl) + .setName("Exclude region end") + .setDesc( + "A line matching this pattern marks the end of an excluded region.", + ) + .addText((text) => + text + .setValue(this.plugin.settings.excludeRegionEnd) + .onChange(async (value) => { + this.plugin.settings.excludeRegionEnd = value; + await this.plugin.saveSettings(); + }), + ); + new Setting(containerEl) .setName("Include not started tasks") .addToggle((toggle) => diff --git a/src/todoService.test.ts b/src/todoService.test.ts index 289cd45..e64a61d 100644 --- a/src/todoService.test.ts +++ b/src/todoService.test.ts @@ -7,6 +7,8 @@ const MOCK_SETTINGS = { todoFilename: "TODO.md", excludeFilePattern: "", excludeFolderFilename: ".exclude_todos", + excludeRegionBegin: "%% exclude: start %%", + excludeRegionEnd: "%% exclude: end %%", useFullFilepath: false, includeNotStarted: true, includeInProgress: true, @@ -380,6 +382,80 @@ describe("TodoService", () => { expect(actual).toEqual(expected); }); + test("parseTodos excludes task items inside exclude regions", () => { + // Arrange + const content = `- [ ] Included +%% exclude: start %% +- [ ] Excluded +- [.] Also excluded +%% exclude: end %% +- [ ] 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 handles multiple exclude regions", () => { + // Arrange + const content = `- [ ] First +%% exclude: start %% +- [ ] Excluded 1 +%% exclude: end %% +- [ ] Second +%% exclude: start %% +- [ ] Excluded 2 +%% exclude: end %% +- [ ] Third`; + + const mockFileService = new MockFileService([]); + + // Act + const todoService = new TodoService(mockFileService, MOCK_SETTINGS); + const actual = todoService.parseTodos(content); + + // Assert + expect(actual.map((t) => t.text)).toEqual([ + "First", + "Second", + "Third", + ]); + }); + + test("parseTodos excludes to end of file when region is not closed", () => { + // Arrange + const content = `- [ ] Included +%% exclude: start %% +- [ ] Excluded`; + + const mockFileService = new MockFileService([]); + + // Act + const todoService = new TodoService(mockFileService, MOCK_SETTINGS); + const actual = todoService.parseTodos(content); + + // Assert + expect(actual.map((t) => t.text)).toEqual(["Included"]); + }); + test("Whole shebang formats TODO.md correctly with task items nested under normal lists", async () => { // Arrange const taskFile = createMockFile( diff --git a/src/todoService.ts b/src/todoService.ts index b4dccbe..b0a0162 100644 --- a/src/todoService.ts +++ b/src/todoService.ts @@ -114,8 +114,20 @@ class TodoService { */ parseTodos(contents: string): Todo[] { const lines = contents.split(/[\r]?[\n]/); + const beginPattern = this.settings.excludeRegionBegin; + const endPattern = this.settings.excludeRegionEnd; + let inExcludedRegion = false; const matchesAndIndices = lines .map((line, index) => { + if (beginPattern && line.trim() === beginPattern) { + inExcludedRegion = true; + } + if (inExcludedRegion) { + if (endPattern && line.trim() === endPattern) { + inExcludedRegion = false; + } + return { match: null as unknown as RegExpMatchArray, index } as IndexMatch; + } const match = line.match(TODO_PATTERN); return { match, index } as IndexMatch; }) From f7f38fabcb5cfca1761f437186dcfa8d3cb476f7 Mon Sep 17 00:00:00 2001 From: joeriddles Date: Tue, 12 May 2026 22:54:55 -0700 Subject: [PATCH 2/3] fix: move new exclude settings Signed-off-by: joeriddles --- src/settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/settings.ts b/src/settings.ts index 93b546a..65c930c 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -99,8 +99,6 @@ class ExtendedTaskListsSettingTab extends PluginSettingTab { }), ); - new Setting(containerEl).setName("Includes").setHeading(); - new Setting(containerEl) .setName("Exclude region begin") .setDesc( @@ -129,6 +127,8 @@ class ExtendedTaskListsSettingTab extends PluginSettingTab { }), ); + new Setting(containerEl).setName("Includes").setHeading(); + new Setting(containerEl) .setName("Include not started tasks") .addToggle((toggle) => From 383ec891802d73c5a007203d4c46c95e2f7f3f2d Mon Sep 17 00:00:00 2001 From: joeriddles Date: Tue, 12 May 2026 22:56:12 -0700 Subject: [PATCH 3/3] chore: format Signed-off-by: joeriddles --- src/todoService.test.ts | 6 +----- src/todoService.ts | 5 ++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/todoService.test.ts b/src/todoService.test.ts index e64a61d..c1d6d32 100644 --- a/src/todoService.test.ts +++ b/src/todoService.test.ts @@ -433,11 +433,7 @@ describe("TodoService", () => { const actual = todoService.parseTodos(content); // Assert - expect(actual.map((t) => t.text)).toEqual([ - "First", - "Second", - "Third", - ]); + expect(actual.map((t) => t.text)).toEqual(["First", "Second", "Third"]); }); test("parseTodos excludes to end of file when region is not closed", () => { diff --git a/src/todoService.ts b/src/todoService.ts index b0a0162..beb3b93 100644 --- a/src/todoService.ts +++ b/src/todoService.ts @@ -126,7 +126,10 @@ class TodoService { if (endPattern && line.trim() === endPattern) { inExcludedRegion = false; } - return { match: null as unknown as RegExpMatchArray, index } as IndexMatch; + return { + match: null as unknown as RegExpMatchArray, + index, + } as IndexMatch; } const match = line.match(TODO_PATTERN); return { match, index } as IndexMatch;