Repository: obsidian-tasks-group/obsidian-tasks
Pull request #1695 (2023-02-26 16:54:55)

feat: Add visual debugging aids for Tasks developers
<!--- Provide a general summary of your changes in the Title above -->

# Description

Add two hidden settings to help developers understand what is going on behind the scenes in the plugin's code.

Note that if sorting is disabled manually in the settings, the `explain` instruction does report this.

From the additions to the CONTRIBUTING file:

---

### How do I enable hidden debugging/visualisation facilities?

> [!Released]
> Introduced in Tasks 1.26.0.

There are some hidden Tasks settings options to turn on some hidden facilities to aid visualising the behaviour of the plugin.

The default values are:

```json
  "debugSettings": {
    "ignoreSortInstructions": false,
    "showTaskHiddenData": false
  }
```

The `data.json` file needs to be edited manually to turn these on: The options are not exposed in the settings UI.

This is what these options do:

- `ignoreSortInstructions`:
  - Turns off all sorting of tasks, that is, it disables both the default sort order and the default sort order.
  - This can be useful if you need a stable order of tasks in order to easily inspect the impact of editing a task line.
- `showTaskHiddenData`:
  - This adjusts the rendering of Task objects to display some extra information, to make the plugin's behaviour easier to inspect.
  - The values display are:
    - Line 1:
      - `task.sectionStart`
      - `task.sectionIndex`
      - `task.precedingHeader`
      - `task.path`
    - Line 2:
      - `task.originalMarkdown`
  - Here is an example of the extra output:
  🐛 **4** . 6 . 'Steps to world domination' . 'ACME.md'
  '`- [ ] #task Feed the baby 🔽 📅 2021-11-21`'


---

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

I have been spending many hours understanding how the `task.sectionStart` and `task.sectionIndex` values change under a myriad of different scenarios, and it has been tremendously helpful to see the values displayed directly in Obsidian, rather than having to read through reams of console debug output.

## How has this been tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

In the test vault, in Obsidian.

## Screenshots (if appropriate)

The following screenshot gives a wealth of information about the behaviour of the plugin:

- `1`:  I had believed that the `task.sectionStart` was the 0-based line number of the previous heading, but this shows that it is the 0-based line number at the start of the list containing the task
- `2`: When toggling tasks rendered in Reading mode, we cannot rely on `task.precedingHeader` as it is not populated
- `3`: When toggling embedded tasks in Reading mode, we also cannot rely on the `task.sectionStart` and `task.sectionIndex` values, as they are 0


![image](https://user-images.githubusercontent.com/4840096/221424634-ab0ba5b9-ce56-4421-a600-1503c0f8f3e8.png)



## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Changes visible to users:

- [x] **New feature** (prefix: `feat` - non-breaking change which adds functionality)
    - But only of use to developers

Internal changes:

- [x] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)
- [x] **Other** Docs added in CONTRIBUTING.md

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My code follows the code style of this project and passes `yarn run lint`.
- [ ] My change requires a change to the documentation.
- [ ] I have [updated the documentation](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#updating-documentation) accordingly.
- [x] My change has adequate [Unit Test coverage](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#maintaining-the-tests).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md) and
[Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md).
Put an x in the boxes to confirm you agree.
-->

- [x] My contribution follow this project's [contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md)
- [x] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)



Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ebefa8a7..e5029378 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -496,6 +496,43 @@ Toggle behavior:
 
 Obsidian writes the changes to disk at its own pace.
 
+### How do I enable hidden debugging/visualisation facilities?
+
+> [!Released]
+> Introduced in Tasks 1.26.0.
+
+There are some hidden Tasks settings options to turn on some hidden facilities to aid visualising the behaviour of the plugin.
+
+The default values are:
+
+```json
+  "debugSettings": {
+    "ignoreSortInstructions": false,
+    "showTaskHiddenData": false
+  }
+```
+
+The `data.json` file needs to be edited manually to turn these on: The options are not exposed in the settings UI.
+
+This is what these options do:
+
+- `ignoreSortInstructions`:
+  - Turns off all sorting of tasks, that is, it disables both the default sort order and the default sort order.
+  - This can be useful if you need a stable order of tasks in order to easily inspect the impact of editing a task line.
+- `showTaskHiddenData`:
+  - This adjusts the rendering of Task objects to display some extra information, to make the plugin's behaviour easier to inspect.
+  - The values display are:
+    - Line 1:
+      - `task.sectionStart`
+      - `task.sectionIndex`
+      - `task.precedingHeader`
+      - `task.path`
+    - Line 2:
+      - `task.originalMarkdown`
+  - Here is an example of the extra output:
+  🐛 **4** . 6 . 'Steps to world domination' . 'ACME.md'
+  '`- [ ] #task Feed the baby 🔽 📅 2021-11-21`'
+
 ### How do I add a new field to the Task class?
 
 - In [tests/Task.test.ts](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/tests/Task.test.ts):
diff --git a/resources/sample_vaults/Tasks-Demo/.obsidian/plugins/obsidian-tasks-plugin/data.json b/resources/sample_vaults/Tasks-Demo/.obsidian/plugins/obsidian-tasks-plugin/data.json
index 11ded090..aab927a6 100644
--- a/resources/sample_vaults/Tasks-Demo/.obsidian/plugins/obsidian-tasks-plugin/data.json
+++ b/resources/sample_vaults/Tasks-Demo/.obsidian/plugins/obsidian-tasks-plugin/data.json
@@ -49,5 +49,9 @@
   "headingOpened": {
     "Core Statuses": true,
     "Custom Statuses": true
+  },
+  "debugSettings": {
+    "ignoreSortInstructions": false,
+    "showTaskHiddenData": false
   }
-}
\ No newline at end of file
+}
diff --git a/src/Config/DebugSettings.ts b/src/Config/DebugSettings.ts
new file mode 100644
index 00000000..aafeade7
--- /dev/null
+++ b/src/Config/DebugSettings.ts
@@ -0,0 +1,11 @@
+export class DebugSettings {
+    constructor(ignoreSortInstructions = false, showTaskHiddenData = false) {
+        this.ignoreSortInstructions = ignoreSortInstructions;
+        this.showTaskHiddenData = showTaskHiddenData;
+    }
+
+    // Optionally disable all sorting of search results, so that tasks are
+    // displayed in the order they appear in the file.
+    readonly ignoreSortInstructions: boolean;
+    readonly showTaskHiddenData: boolean;
+}
diff --git a/src/Config/Settings.ts b/src/Config/Settings.ts
index 084e7cfe..2fa8c624 100644
--- a/src/Config/Settings.ts
+++ b/src/Config/Settings.ts
@@ -1,5 +1,6 @@
 import { StatusConfiguration } from '../StatusConfiguration';
 import { Status } from '../Status';
+import { DebugSettings } from './DebugSettings';
 import { StatusSettings } from './StatusSettings';
 import { Feature } from './Feature';
 import type { FeatureFlag } from './Feature';
@@ -35,6 +36,7 @@ export interface Settings {
 
     // Tracks the stage of the headings in the settings UI.
     headingOpened: HeadingState;
+    debugSettings: DebugSettings;
 }
 
 const defaultSettings: Settings = {
@@ -59,6 +61,7 @@ const defaultSettings: Settings = {
         // setDoneDate: true,
     },
     headingOpened: {},
+    debugSettings: new DebugSettings(),
 };
 
 let settings: Settings = { ...defaultSettings };
diff --git a/src/Query/Query.ts b/src/Query/Query.ts
index 86ea2191..2c758d54 100644
--- a/src/Query/Query.ts
+++ b/src/Query/Query.ts
@@ -102,6 +102,12 @@ export class Query implements IQuery {
             result += '.\n';
         }
 
+        const { debugSettings } = getSettings();
+        if (debugSettings.ignoreSortInstructions) {
+            result +=
+                "\n\nNOTE: All sort instructions, including default sort order, are disabled, due to 'ignoreSortInstructions' setting.";
+        }
+
         return result;
     }
 
@@ -134,7 +140,9 @@ export class Query implements IQuery {
             tasks = tasks.filter(filter.filterFunction);
         });
 
-        const tasksSortedLimited = Sort.by(this.sorting, tasks).slice(0, this.limit);
+        const { debugSettings } = getSettings();
+        const tasksSorted = debugSettings.ignoreSortInstructions ? tasks : Sort.by(this.sorting, tasks);
+        const tasksSortedLimited = tasksSorted.slice(0, this.limit);
         return Group.by(this.grouping, tasksSortedLimited);
     }
 
diff --git a/src/TaskLineRenderer.ts b/src/TaskLineRenderer.ts
index f2bbcb26..89844bd8 100644
--- a/src/TaskLineRenderer.ts
+++ b/src/TaskLineRenderer.ts
@@ -109,6 +109,12 @@ async function taskToHtml(
         }
     }
 
+    const { debugSettings } = getSettings();
+    if (debugSettings.showTaskHiddenData) {
+        // Add some debug output to enable hidden information in the task to be inspected.
+        taskAsString += `<br>🐛 <b>${task.sectionStart}</b> . ${task.sectionIndex} . '${task.precedingHeader}' . '${task.path}'<br>'<code>${task.originalMarkdown}</code>'<br>`;
+    }
+
     await renderComponentText(parentElement, taskAsString, 'description', task, textRenderer);
 }
 


Test patch:
diff --git a/tests/Config/DebugSettings.test.ts b/tests/Config/DebugSettings.test.ts
new file mode 100644
index 00000000..725fcb96
--- /dev/null
+++ b/tests/Config/DebugSettings.test.ts
@@ -0,0 +1,45 @@
+import { DebugSettings } from '../../src/Config/DebugSettings';
+import { resetSettings, updateSettings } from '../../src/Config/Settings';
+import { Query } from '../../src/Query/Query';
+import { createTasksFromMarkdown } from '../TestHelpers';
+
+describe('DebugSettings', () => {
+    afterEach(() => {
+        resetSettings();
+    });
+
+    it('should disable sorting instructions if', () => {
+        // Arrange
+        const tasksAsMarkdown = `
+- [x] Task 1 - should not appear in output
+- [x] Task 2 - should not appear in output
+- [ ] Task 3 - will be sorted to 1st place, so should pass limit
+`;
+        const tasks = createTasksFromMarkdown(tasksAsMarkdown, 'some_markdown_file', 'Some Heading');
+
+        const query = new Query({
+            source: `
+            sort by status
+            explain
+        `,
+        }); // Would put Task 3 first
+
+        // Disable sort instructions
+        updateSettings({ debugSettings: new DebugSettings(true) });
+
+        // Act
+        const groups = query.applyQueryToTasks(tasks);
+
+        // Assert
+        expect(groups.groups.length).toEqual(1);
+        const soleTaskGroup = groups.groups[0];
+        // Check that the tasks are found in the original order, not the order in the sort instruction
+        expect('\n' + soleTaskGroup.tasksAsStringOfLines()).toStrictEqual(tasksAsMarkdown);
+
+        expect(query.explainQueryWithoutIntroduction()).toMatchInlineSnapshot(`
+            "No filters supplied. All tasks will match the query.
+
+            NOTE: All sort instructions, including default sort order, are disabled, due to 'ignoreSortInstructions' setting."
+        `);
+    });
+});
diff --git a/tests/TaskLineRenderer.test.ts b/tests/TaskLineRenderer.test.ts
index ac90c738..eb7b8adf 100644
--- a/tests/TaskLineRenderer.test.ts
+++ b/tests/TaskLineRenderer.test.ts
@@ -2,6 +2,7 @@
  * @jest-environment jsdom
  */
 import moment from 'moment';
+import { DebugSettings } from '../src/Config/DebugSettings';
 import { renderTaskLine } from '../src/TaskLineRenderer';
 import { resetSettings, updateSettings } from '../src/Config/Settings';
 import { LayoutOptions } from '../src/TaskLayout';
@@ -44,6 +45,10 @@ function getDescriptionText(parentElement: HTMLElement) {
 }
 
 describe('task line rendering', () => {
+    afterEach(() => {
+        resetSettings();
+    });
+
     it('creates the correct span structure for a basic task', async () => {
         const taskLine = '- [ ] This is a simple task';
         const task = fromLine({
@@ -97,6 +102,8 @@ describe('task line rendering', () => {
     ) => {
         const task = fromLine({
             line: taskLine,
+            path: 'a/b/c.d',
+            precedingHeader: 'Previous Heading',
         });
         const fullLayoutOptions = { ...new LayoutOptions(), ...layoutOptions };
         const parentRender = await createMockParentAndRender(task, fullLayoutOptions);
@@ -176,6 +183,16 @@ describe('task line rendering', () => {
         );
     });
 
+    it('renders debug info if requested', async () => {
+        // Disable sort instructions
+        updateSettings({ debugSettings: new DebugSettings(false, true) });
+        await testLayoutOptions(
+            '- [ ] Task with invalid due date 📅 2023-11-02',
+            {},
+            "Task with invalid due date 📅 2023-11-02<br>🐛 <b>0</b> . 0 . 'Previous Heading' . 'a/b/c.d'<br>'<code>- [ ] Task with invalid due date 📅 2023-11-02</code>'<br>",
+        );
+    });
+
     it('standardise the recurrence rule, even if the rule is invalid', async () => {
         await testLayoutOptions(
             '- [ ] Task with invalid recurrence rule 🔁 every month on the 32nd',