Skip to content

Commit 20dbc2d

Browse files
authored
Merge pull request #2539 from obsidian-tasks-group/explain-sortby
feat: Make 'explain' show 'sort by' commands
2 parents fa8562d + 3c9d30e commit 20dbc2d

26 files changed

+128
-6
lines changed

Diff for: docs/Introduction.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ _In recent [releases](https://github.com/obsidian-tasks-group/obsidian-tasks/rel
1414
-->
1515

1616
- X.Y.Z: 🔥 Add [[Layout#Full Mode|'full mode']] to turn off `short mode`.
17-
- X.Y.Z: 🔥 Add any [[Grouping|'group by']] instructions to [[Explaining Queries|explain]] output.
17+
- X.Y.Z: 🔥 Add any [[Grouping|'group by']] and [[Sorting|'sort by']] instructions to [[Explaining Queries|explain]] output.
1818
- 5.3.0: 🔥 Add [[Postponing|postpone button]] to Tasks query results.
1919
- 5.3.0: 🔥 Add [[Toggling and Editing Statuses#'Change task status' context menu|'change task status' menu]] to Reading mode and Tasks query results.
2020
- 5.3.0: 🔥 Add documentation section about [[About Editing|editing tasks]].

Diff for: docs/Queries/Explaining Queries.md

+15
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This has a number of benefits:
2121
- This often explains why tasks are missing from results.
2222
- If there is a [[Global Query|global query]] enabled, it too is included in the explanation.
2323
- Any [[Grouping|'group by']] instructions are listed (since Tasks X.Y.Z)
24+
- Any [[Sorting|'sort by']] instructions are listed (since Tasks X.Y.Z)
2425

2526
## Examples
2627

@@ -53,6 +54,8 @@ Explanation of this Tasks code block query:
5354
due date is before 2022-10-22 (Saturday 22nd October 2022)
5455
5556
No grouping instructions supplied.
57+
58+
No sorting instructions supplied.
5659
```
5760
<!-- endSnippet -->
5861

@@ -84,6 +87,8 @@ Explanation of this Tasks code block query:
8487
using regex: '^Root\/Sub-Folder\/Sample File\.md' with flag 'i'
8588
8689
No grouping instructions supplied.
90+
91+
No sorting instructions supplied.
8792
```
8893
<!-- endSnippet -->
8994

@@ -113,6 +118,8 @@ Explanation of this Tasks code block query:
113118
is recurring
114119
115120
No grouping instructions supplied.
121+
122+
No sorting instructions supplied.
116123
```
117124
<!-- endSnippet -->
118125

@@ -157,6 +164,8 @@ Explanation of this Tasks code block query:
157164
description includes 7
158165
159166
No grouping instructions supplied.
167+
168+
No sorting instructions supplied.
160169
```
161170
<!-- endSnippet -->
162171

@@ -194,6 +203,8 @@ Explanation of the global query:
194203
195204
No grouping instructions supplied.
196205
206+
No sorting instructions supplied.
207+
197208
At most 50 tasks.
198209
199210
Explanation of this Tasks code block query:
@@ -206,6 +217,8 @@ Explanation of this Tasks code block query:
206217
2022-10-30 (Sunday 30th October 2022) inclusive
207218
208219
No grouping instructions supplied.
220+
221+
No sorting instructions supplied.
209222
```
210223
<!-- endSnippet -->
211224

@@ -245,6 +258,8 @@ Explanation of this Tasks code block query:
245258
description includes Some Cryptic String
246259
247260
No grouping instructions supplied.
261+
262+
No sorting instructions supplied.
248263
```
249264
<!-- endSnippet -->
250265

Diff for: docs/Queries/Line Continuations.md

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Explanation of this Tasks code block query:
3737
priority is lowest
3838
3939
No grouping instructions supplied.
40+
41+
No sorting instructions supplied.
4042
```
4143
<!-- endSnippet -->
4244

@@ -77,6 +79,8 @@ Explanation of this Tasks code block query:
7779
description includes \
7880
7981
No grouping instructions supplied.
82+
83+
No sorting instructions supplied.
8084
```
8185
<!-- endSnippet -->
8286

Diff for: docs/Queries/Regular Expressions.md

+2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ Explanation of this Tasks code block query:
9999
using regex: '^Root\/Sub-Folder\/Sample File\.md' with flag 'i'
100100
101101
No grouping instructions supplied.
102+
103+
No sorting instructions supplied.
102104
```
103105
<!-- endSnippet -->
104106

Diff for: docs/Scripting/Placeholders.md

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ Explanation of this Tasks code block query:
5454
description includes Some Cryptic String
5555
5656
No grouping instructions supplied.
57+
58+
No sorting instructions supplied.
5759
```
5860
<!-- endSnippet -->
5961

Diff for: src/Query/Explain/Explainer.ts

+14
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class Explainer {
3434
const results: string[] = [];
3535
results.push(this.explainFilters(query));
3636
results.push(this.explainGroups(query));
37+
results.push(this.explainSorters(query));
3738
results.push(this.explainQueryLimits(query));
3839
results.push(this.explainDebugSettings());
3940

@@ -73,6 +74,19 @@ export class Explainer {
7374
return result;
7475
}
7576

77+
public explainSorters(query: Query) {
78+
const numberOfSorters = query.sorting.length;
79+
if (numberOfSorters === 0) {
80+
return this.indent('No sorting instructions supplied.\n');
81+
}
82+
83+
let result = '';
84+
for (let i = 0; i < numberOfSorters; i++) {
85+
result += this.indentation + query.sorting[i].instruction + '\n';
86+
}
87+
return result;
88+
}
89+
7690
public explainQueryLimits(query: Query) {
7791
function getPluralisedText(limit: number) {
7892
let text = `At most ${limit} task`;

Diff for: src/Query/Filter/Field.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,21 @@ export abstract class Field {
175175
return new RegExp(`^sort by ${this.fieldNameSingularEscaped()}( reverse)?`, 'i');
176176
}
177177

178+
/**
179+
* Reconstruct a 'sorter by' instruction to use for sorting of this field.
180+
*
181+
* This is used to simplify the construction of {@link Sorter} objects.
182+
* @param reverse
183+
* @protected
184+
*/
185+
protected sorterInstruction(reverse: boolean) {
186+
let instruction = `sort by ${this.fieldNameSingular()}`;
187+
if (reverse) {
188+
instruction += ' reverse';
189+
}
190+
return instruction;
191+
}
192+
178193
/**
179194
* Return a function to compare two Task objects, for use in sorting by this field's value.
180195
*
@@ -190,7 +205,7 @@ export abstract class Field {
190205
* @param reverse - false for normal sort order, true for reverse sort order.
191206
*/
192207
public createSorter(reverse: boolean): Sorter {
193-
return new Sorter(this.fieldNameSingular(), this.comparator(), reverse);
208+
return new Sorter(this.sorterInstruction(reverse), this.fieldNameSingular(), this.comparator(), reverse);
194209
}
195210

196211
/**

Diff for: src/Query/Filter/TagsField.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class TagsField extends MultiTextField {
7272
const reverse = !!match[1];
7373
const propertyInstance = isNaN(+match[2]) ? 1 : +match[2];
7474
const comparator = TagsField.makeCompareByTagComparator(propertyInstance);
75-
return new Sorter(this.fieldNameSingular(), comparator, reverse);
75+
return new Sorter(line, this.fieldNameSingular(), comparator, reverse);
7676
}
7777

7878
/**

Diff for: src/Query/Sorter.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@ export type Comparator = (a: Task, b: Task) => number;
1616
* It stores the comparison function as a {@link Comparator}.
1717
*/
1818
export class Sorter {
19+
public readonly instruction: string;
1920
public readonly property: string;
2021
public readonly comparator: Comparator;
2122

2223
/**
2324
* Constructor.
2425
*
26+
* @param instruction - the query instruction that created this object
2527
* @param property - the name of the property.
2628
* @param comparator - {@link Comparator} function, for sorting in the standard direction.
2729
* If `reverse` is true, it will automatically be converted to reverse the sort direction.
2830
* @param reverse - whether the sort order should be reversed.
2931
*/
30-
constructor(property: string, comparator: Comparator, reverse: boolean) {
32+
constructor(instruction: string, property: string, comparator: Comparator, reverse: boolean) {
33+
this.instruction = instruction;
3134
this.property = property;
3235
this.comparator = Sorter.maybeReverse(reverse, comparator);
3336
}

Diff for: tests/Config/DebugSettings.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ describe('DebugSettings', () => {
3939
4040
No grouping instructions supplied.
4141
42+
sort by status
43+
4244
NOTE: All sort instructions, including default sort order, are disabled, due to 'ignoreSortInstructions' setting.
4345
"
4446
`);

Diff for: tests/Query.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ describe('Query parsing', () => {
268268
expect(query.error).toBeUndefined();
269269
expect(query.sorting.length).toEqual(1);
270270
expect(query.sorting[0]).toBeDefined();
271+
expect(query.sorting[0].instruction).toEqual(filter);
271272

272273
// Assert Uppercase
273274
expect(queryUpperCase.error).toBeUndefined();
@@ -1197,6 +1198,8 @@ describe('Query', () => {
11971198
"description includes hello
11981199
11991200
No grouping instructions supplied.
1201+
1202+
No sorting instructions supplied.
12001203
"
12011204
`);
12021205
});
@@ -1438,6 +1441,8 @@ with \ backslash)`;
14381441
description includes line 1 continued with \\ backslash
14391442
14401443
No grouping instructions supplied.
1444+
1445+
No sorting instructions supplied.
14411446
"
14421447
`);
14431448
expect(queryUpperCase.explainQuery()).toMatchInlineSnapshot(`
@@ -1447,6 +1452,8 @@ with \ backslash)`;
14471452
description includes line 1 continued with \\ backslash
14481453
14491454
No grouping instructions supplied.
1455+
1456+
No sorting instructions supplied.
14501457
"
14511458
`);
14521459
});

Diff for: tests/Query/Explain/DocsSamplesForExplain.test.explain_boolean_combinations.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ Explanation of this Tasks code block query:
88
is recurring
99

1010
No grouping instructions supplied.
11+
12+
No sorting instructions supplied.

Diff for: tests/Query/Explain/DocsSamplesForExplain.test.explain_expands_dates.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ Explanation of this Tasks code block query:
1010
due date is before 2022-10-22 (Saturday 22nd October 2022)
1111

1212
No grouping instructions supplied.
13+
14+
No sorting instructions supplied.

Diff for: tests/Query/Explain/DocsSamplesForExplain.test.explain_explains_task_block_with_global_query_active.approved.explanation.text

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Explanation of the global query:
44

55
No grouping instructions supplied.
66

7+
No sorting instructions supplied.
8+
79
At most 50 tasks.
810

911
Explanation of this Tasks code block query:
@@ -16,3 +18,5 @@ Explanation of this Tasks code block query:
1618
2022-10-30 (Sunday 30th October 2022) inclusive
1719

1820
No grouping instructions supplied.
21+
22+
No sorting instructions supplied.

Diff for: tests/Query/Explain/DocsSamplesForExplain.test.explain_line_continuation_-_double_slash.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ Explanation of this Tasks code block query:
33
description includes \
44

55
No grouping instructions supplied.
6+
7+
No sorting instructions supplied.

Diff for: tests/Query/Explain/DocsSamplesForExplain.test.explain_line_continuation_-_single_slash.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ Explanation of this Tasks code block query:
66
priority is lowest
77

88
No grouping instructions supplied.
9+
10+
No sorting instructions supplied.

Diff for: tests/Query/Explain/DocsSamplesForExplain.test.explain_nested_boolean_combinations.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ Explanation of this Tasks code block query:
1515
description includes 7
1616

1717
No grouping instructions supplied.
18+
19+
No sorting instructions supplied.

Diff for: tests/Query/Explain/DocsSamplesForExplain.test.explain_placeholders.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ Explanation of this Tasks code block query:
1111
description includes Some Cryptic String
1212

1313
No grouping instructions supplied.
14+
15+
No sorting instructions supplied.

Diff for: tests/Query/Explain/DocsSamplesForExplain.test.explain_regular_expression.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ Explanation of this Tasks code block query:
44
using regex: '^Root\/Sub-Folder\/Sample File\.md' with flag 'i'
55

66
No grouping instructions supplied.
7+
8+
No sorting instructions supplied.

Diff for: tests/Query/Explain/Explainer.test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ limit groups 3
6565
group by priority reverse
6666
group by happens
6767
68+
sort by description reverse
69+
sort by path
70+
6871
At most 50 tasks.
6972
7073
At most 3 tasks per group (if any "group by" options are supplied).
@@ -91,6 +94,9 @@ limit groups 3
9194
group by priority reverse
9295
group by happens
9396
97+
sort by description reverse
98+
sort by path
99+
94100
At most 50 tasks.
95101
96102
At most 3 tasks per group (if any "group by" options are supplied).
@@ -146,6 +152,18 @@ describe('explain groupers', () => {
146152
});
147153
});
148154

155+
describe('explain sorters', () => {
156+
it('should explain "sort by" options', () => {
157+
const source = 'sort by due\nsort by priority()';
158+
const query = new Query(source);
159+
expect(explainer.explainSorters(query)).toMatchInlineSnapshot(`
160+
"sort by due
161+
sort by priority
162+
"
163+
`);
164+
});
165+
});
166+
149167
describe('explain limits', () => {
150168
it('should explain limit 5', () => {
151169
const source = 'limit 5';

Diff for: tests/Query/Filter/ReferenceDocs/FilterReference/DateFieldReference.test.explain_date_reference_filters-date-examples.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ due May =>
3333
due date is on 2023-05-01 (Monday 1st May 2023)
3434

3535
No grouping instructions supplied.
36+
37+
No sorting instructions supplied.

Diff for: tests/Query/Filter/ReferenceDocs/FilterReference/DateFieldReference.test.explain_date_reference_last-this-next-week-month-quarter-year.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,5 @@ due next year =>
7474
description includes ------------------------------
7575

7676
No grouping instructions supplied.
77+
78+
No sorting instructions supplied.

Diff for: tests/Query/Filter/ReferenceDocs/FilterReference/DateFieldReference.test.explain_date_reference_last-this-next-weekday.approved.explanation.text

+2
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,5 @@ due next sunday =>
104104
description includes ------------------------------
105105

106106
No grouping instructions supplied.
107+
108+
No sorting instructions supplied.

Diff for: tests/Query/Filter/TextField.test.explains_regular_expression_searches_bulk_test.approved.txt

+2
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,6 @@ tags regex matches /(home|pc_mac|town)/ =>
108108

109109
No grouping instructions supplied.
110110

111+
No sorting instructions supplied.
112+
111113
Error: None

Diff for: tests/Sort.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ describe('Sort', () => {
3131

3232
// Normal way round
3333
{
34-
const sortByDescriptionLength = new Sorter('junk', comparator, false);
34+
const sortByDescriptionLength = new Sorter('sort by description length', 'junk', comparator, false);
3535
expect(sortByDescriptionLength.comparator(short, long)).toEqual(1);
3636
expect(sortByDescriptionLength.comparator(short, short)).toEqual(0);
3737
expect(sortByDescriptionLength.comparator(long, short)).toEqual(-1);
3838
}
3939

4040
// Reversed
4141
{
42-
const sortByDescriptionLength = new Sorter('junk', comparator, true);
42+
const sortByDescriptionLength = new Sorter('sort by description length reverse', 'junk', comparator, true);
4343
expect(sortByDescriptionLength.comparator(short, long)).toEqual(-1);
4444
expect(sortByDescriptionLength.comparator(short, short)).toEqual(-0);
4545
expect(sortByDescriptionLength.comparator(long, short)).toEqual(1);

0 commit comments

Comments
 (0)