diff --git a/README.md b/README.md index 07d8046f92..fe1c979cad 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,11 @@ The following filters exist: - `due (before|after|on) ` - `path (includes|does not include) ` - `description (includes|does not include) ` +- `heading (includes|does not include) ` + - Whether or not the heading preceding the task includes the given string. + - Always tries to match the closest heading above the task, regardless of heading level. + - Will never match a task that does not have a preceding heading in its file. + - Matches case-insensitive (disregards capitalization). - `exclude sub-items` - When this is set, the result list will only include tasks that are not indented in their file. It will only show tasks that are top level list items in their list. - `limit to tasks` @@ -241,6 +246,13 @@ All open tasks that are due within the next two weeks, but are not overdue (due due before in two weeks ``` +All done tasks that are anywhere in the vault under a `tasks` heading (e.g. `## Tasks`): + + ```tasks + done + heading includes tasks + ``` + Show all tasks that aren't done, are due on the 9th of April 2021, and where the path includes `GitHub`: ```tasks diff --git a/src/Query.ts b/src/Query.ts index 0003144d3f..6b538bf8ec 100644 --- a/src/Query.ts +++ b/src/Query.ts @@ -15,6 +15,7 @@ export class Query { private readonly doneRegexp = /done (before|after|on)? ?(.*)/; private readonly pathRegexp = /path (includes|does not include) (.*)/; private readonly descriptionRegexp = /description (includes|does not include) (.*)/; + private readonly headingRegexp = /heading (includes|does not include) (.*)/; private readonly limitRegexp = /limit (to )?(\d+)( tasks?)?/; private readonly excludeSubItemsString = 'exclude sub-items'; @@ -54,6 +55,9 @@ export class Query { case this.descriptionRegexp.test(line): this.parseDescriptionFilter({ line }); break; + case this.headingRegexp.test(line): + this.parseHeadingFilter({ line }); + break; case this.limitRegexp.test(line): this.parseLimit({ line }); break; @@ -166,6 +170,34 @@ export class Query { } } + private parseHeadingFilter({ line }: { line: string }): void { + const headingMatch = line.match(this.headingRegexp); + if (headingMatch !== null) { + const filterMethod = headingMatch[1].toLowerCase(); + if (filterMethod === 'includes') { + this._filters.push( + (task: Task) => + task.precedingHeader !== null && + task.precedingHeader + .toLowerCase() + .includes(headingMatch[2]), + ); + } else if (headingMatch[1] === 'does not include') { + this._filters.push( + (task: Task) => + task.precedingHeader !== null && + !task.precedingHeader + .toLowerCase() + .includes(headingMatch[2]), + ); + } else { + this._error = 'do not understand query filter (heading)'; + } + } else { + this._error = 'do not understand query filter (heading)'; + } + } + private parseLimit({ line }: { line: string }): void { const limitMatch = line.match(this.limitRegexp); if (limitMatch !== null) {