Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tasks rendered in Reading Mode are not always updated when source file is edited #1680

Closed
2 of 7 tasks
claremacrae opened this issue Feb 22, 2023 · 2 comments · Fixed by #1780
Closed
2 of 7 tasks
Assignees
Labels
display: reading mode Issues referring to Obsidian's Reading Mode or Reading View scope: data integrity Problems that may cause loss or corruption of user data type: bug Something isn't working

Comments

@claremacrae
Copy link
Collaborator

claremacrae commented Feb 22, 2023

Please check that this issue hasn't been reported before.

  • I searched previous Bug Reports didn't find any similar reports.

Expected Behavior

This is initially a placeholder report: I will edit and add more info as I find more out.

Given a file is open in both two panes:

  • an editing mode (Source or Live Preview)
  • Reading mode

When I add or remove lines from the file

Then rendered tasks in the Reading pane should always be updated, so that that when I toggle them, the correct line is toggled.

Current behaviour

Given a file is open in both two panes:

  • an editing mode (Source or Live Preview)
  • Reading mode

When I add or remove lines from the file

Then rendered tasks in the Reading pane are only updated when any tasks in the file are edited

Steps to reproduce

Summary

The steps below make some changes to the Tasks source code so that some internal information is made visible.

Whenever Tasks renders a task, this code will add the following line for debugging purposes:

image

The information is:

${task.sectionStart}</b> . ${task.sectionIndex} . '${task.precedingHeader}

Making this information visible has revealed that in Reading mode, the rendering of tasks is not updated when lines are added and removed from the source file.

I believe that this is one fundamental cause of Tasks sometimes updating the wrong source line.

Steps

Now experiment.

Initial Display

Initially, the sectionStart and sectionIndex are consistent between rendered tasks and rendered task blocks in Reading mode

image

Other things to note:

  • The rendered tasks have a null precedingHeader because InlineRenderer does not populate that information
  • The task.sectionIndex is actually the 0-based index of the first task in a section, not the 0-based index of the section heading

Experiment

Add and remove some lines in the source view, in various locations.

You will see that the position numbers in the tasks block are updated, but the ones in the rendered tasks are not.

image

The patches

These are based on code in my fork, at this revision: claremacrae@dfb720e

Index: src/Cache.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/Cache.ts b/src/Cache.ts
--- a/src/Cache.ts	(revision dfb720eaa5dc16a5a89168dd0fea35b45b061b77)
+++ b/src/Cache.ts	(date 1677056378300)
@@ -212,7 +212,8 @@
             // if (this.getState() == State.Warm) {
             //     console.debug(`Tasks unchanged in ${file.path}`);
             // }
-            return;
+            // Disable optimisation that prevents redrawing of Tasks code blocks if tasks are unchanged when a file is updated
+            // return;
         }
 
         if (this.getState() == State.Warm) {
Index: src/InlineRenderer.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/InlineRenderer.ts b/src/InlineRenderer.ts
--- a/src/InlineRenderer.ts	(revision dfb720eaa5dc16a5a89168dd0fea35b45b061b77)
+++ b/src/InlineRenderer.ts	(date 1677051433794)
@@ -69,7 +69,7 @@
                 path,
                 sectionStart: section.lineStart,
                 sectionIndex,
-                precedingHeader: null, // We don't need the preceding header for in-line rendering.
+                precedingHeader: null, // TODO The previous heading would be really useful, to aid in finding the correct task for toggling
                 fallbackDate: null, // We don't need the fallback date for in-line rendering
             });
             if (task !== null) {
Index: src/Query/Query.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/Query/Query.ts b/src/Query/Query.ts
--- a/src/Query/Query.ts	(revision dfb720eaa5dc16a5a89168dd0fea35b45b061b77)
+++ b/src/Query/Query.ts	(date 1677056729091)
@@ -2,7 +2,7 @@
 import type { Task } from '../Task';
 import type { IQuery } from '../IQuery';
 import { getSettings } from '../Config/Settings';
-import { Sort } from './Sort';
+// import { Sort } from './Sort';
 import type { Sorter } from './Sorter';
 import type { TaskGroups } from './TaskGroups';
 import * as FilterParser from './FilterParser';
@@ -134,7 +134,9 @@
             tasks = tasks.filter(filter.filterFunction);
         });
 
-        const tasksSortedLimited = Sort.by(this.sorting, tasks).slice(0, this.limit);
+        // Disabling all sorting, so that tasks are displayed in the order they appear in the file
+        // const tasksSortedLimited = Sort.by(this.sorting, tasks).slice(0, this.limit);
+        const tasksSortedLimited = tasks;
         return Group.by(this.grouping, tasksSortedLimited);
     }
 
Index: src/QueryRenderer.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/QueryRenderer.ts b/src/QueryRenderer.ts
--- a/src/QueryRenderer.ts	(revision dfb720eaa5dc16a5a89168dd0fea35b45b061b77)
+++ b/src/QueryRenderer.ts	(date 1677056729087)
@@ -154,7 +154,8 @@
                 content.appendChild(taskList);
             }
             const totalTasksCount = tasksSortedLimitedGrouped.totalTasksCount();
-            console.debug(`${totalTasksCount} of ${tasks.length} tasks displayed in a block in "${this.filePath}"`);
+            // Disable debug message logging number of tasks displayed in Tasks block
+            // console.debug(`${totalTasksCount} of ${tasks.length} tasks displayed in a block in "${this.filePath}"`);
             this.addTaskCount(content, totalTasksCount);
         } else if (this.query.error !== undefined) {
             content.createDiv().innerHTML =
Index: src/TaskLineRenderer.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/TaskLineRenderer.ts b/src/TaskLineRenderer.ts
--- a/src/TaskLineRenderer.ts	(revision dfb720eaa5dc16a5a89168dd0fea35b45b061b77)
+++ b/src/TaskLineRenderer.ts	(date 1677019042572)
@@ -108,6 +108,7 @@
             taskAsString += componentString;
         }
     }
+    taskAsString += `<br>🐛 <b>${task.sectionStart}</b> . ${task.sectionIndex} . '${task.precedingHeader}'<br>`;
 
     await renderComponentText(parentElement, taskAsString, 'description', task, textRenderer);
 }
Index: src/File.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/File.ts b/src/File.ts
--- a/src/File.ts	(revision dfb720eaa5dc16a5a89168dd0fea35b45b061b77)
+++ b/src/File.ts	(date 1677011294708)
@@ -123,7 +123,8 @@
     let listItem: ListItemCache | undefined;
     let sectionIndex = 0;
     for (const listItemCache of listItemsCache) {
-        if (listItemCache.position.start.line < originalTask.sectionStart) {
+        const lineIndex = listItemCache.position.start.line;
+        if (lineIndex < originalTask.sectionStart) {
             continue;
         }
 
@@ -131,7 +132,7 @@
             continue;
         }
 
-        const line = fileLines[listItemCache.position.start.line];
+        const line = fileLines[lineIndex];
         if (line.includes(globalFilter)) {
             if (sectionIndex === originalTask.sectionIndex) {
                 const dateFromFileName = new Lazy(() => DateFallback.fromPath(originalTask.path));
@@ -151,10 +152,15 @@
 Expected task:
 ${originalTask.originalMarkdown}
 Found task line:
-${line}`;
+${line}
+Other info:
+    previousTries: ${previousTries}
+    originalTask.sectionStart: ${originalTask.sectionStart}
+    originalTask.sectionIndex: ${originalTask.sectionIndex}
+`;
                     console.error(message);
-                    new Notice(message);
-                    return;
+                    new Notice(message, 10000);
+                    return retry();
                 }
                 break;
             }

Which Operating Systems are you using?

  • Android
  • iPhone/iPad
  • Linux
  • macOS
  • Windows

Obsidian Version

1.1.14

Tasks Plugin Version

1.25.0

Checks

  • I have tried it with all other plugins disabled and the error still occurs

Possible solution

I suspect the problem is that InlineRenderer should probably use MarkdownPostProcessorContext.addChild()

This is based on these instructions:

https://github.com/obsidianmd/obsidian-api/blob/bceb489fc25ceba5973119d6e57759d64850f90d/obsidian.d.ts#L1927-L1936

@claremacrae claremacrae added type: bug Something isn't working scope: data integrity Problems that may cause loss or corruption of user data help wanted Extra attention is needed labels Feb 22, 2023
@schemar
Copy link
Collaborator

schemar commented Mar 3, 2023

Hey Clare, this is good information. It clarifies the issue a lot!
Below, I shared some thoughts I have. However, I don't have a solution 😞 Unless, of course, re-rendering on cache updates works "good enough" 😅

Cache Events

The QueryRenderer listens to cache updates and redraws its tasks:

// Line 91:
this.renderEventRef = this.events.onCacheUpdate(this.render.bind(this));

The InlineRenderer does no such thing. As a quick fix, the InlineRenderer could also listen to cache events and re-render its tasks accordingly. It's important to register and unregister the event listeners correctly so that you don't produce a memory leak.

This solution could potentially be inefficient. However, since I assume a limited number of open panes at any time, it could be fine.

Inline Renderer's Task Handling

Another thing I noticed are InlineRenderer's lines 82ff.:

const task = fileTasks[sectionIndex];
const renderedElement = renderedElements[sectionIndex];

if (task === undefined || renderedElement === undefined) {
    // Assuming match of tasks in file and render preview.
    // If there is a mis-match in the numbers, we still process
    // what we can.
    continue;
}

The comment before the continue looks like it shouldn't silently continue, but re-render everything or something. I am not sure with this one and I don't think it's directly related (since the task count is correct), but maybe it helps.

"Correct" Solution

I don't know what the "correct" solution would be. Replacing the inline HTML has been dirty from the beginning. It was a kind of 80/20 solution. But I also don't know what better to do if Tasks should support rendering tasks with, for example, the setting "remove global filter".
The alternative, only replacing the click handler on the checkbox, would also break here, I think, as it wouldn't get updated the same way the whole line doesn't get updated 🤔

@claremacrae claremacrae self-assigned this Mar 5, 2023
@claremacrae claremacrae removed the help wanted Extra attention is needed label Mar 5, 2023
@claremacrae claremacrae moved this from 🔖 Ready to 🏗 In progress in Tasks (Obsidian plugin) Roadmap Mar 6, 2023
@claremacrae
Copy link
Collaborator Author

claremacrae commented Mar 7, 2023

Some notes on the behaviour as a consequence of this issue...

Setup

  1. In all cases, start by creating a file with following content.
  2. Then open that side-by-side file in two panes
    • One in Source mode
    • One in Reading mode
  3. Steps to reproduce the issue
    • Source pane: Select some blank lines in the left hand side
    • Reading pain: Point the cursor over the last checkbox
    • Hit the delete key
    • Reading pain: Quickly toggle the last checkbox

Note: The blank lines are important, so if in the Tasks repo, don't run yarn lint:markdown as it will remove duplicate blank lines.

# 2 Headings








## Heading 1

- [ ] #task Heading 1/Task 1
- [ ] #task Heading 1/Task 2

## Heading 2

- [ ] #task Heading 2/Task 1
- [ ] #task Heading 2/Task 2

Behaviour in Tasks 1.25.0 - wrong line overwritten

  • Sometimes the toggled task is saved in the correct place
  • But sometimes:
    • The line with the 4th task is unchanged
    • And the toggled line (- [x] #task Heading 2/Task 2 ✅ 2023-03-07) is pasted over one of the tasks in Heading 1 section

With feaafaf - latest code on main - console error

With a build of the code on feaafaf the behaviour is a console error, and the markdown file does not change at all.

plugin:obsidian-tasks-plugin:17605 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'includes')
    at eval (plugin:obsidian-tasks-plugin:17605:14)
    at Generator.next (<anonymous>)
    at fulfilled (plugin:obsidian-tasks-plugin:173:24)

The 4th task then becomes unresponsive to clicks.

The traceback points to line 166 here, so the line number in the task is larger than the number of lines in the file, and line is undefined:

obsidian-tasks/src/File.ts

Lines 165 to 172 in feaafaf

const line = fileLines[listItemCache.position.start.line];
if (line.includes(globalFilter)) {
if (sectionIndex === originalTask.sectionIndex) {
if (line === originalTask.originalMarkdown) {
listItem = listItemCache;
} else {
errorAndNotice(
`Tasks: Unable to find task in file ${originalTask.path}.


Update 2023-03-20 - that traceback has been fixed in #1772.

@claremacrae claremacrae moved this from 🏗 In progress to 🔖 Ready in Tasks (Obsidian plugin) Roadmap Mar 12, 2023
claremacrae added a commit that referenced this issue Mar 20, 2023
See the comment #1680 (comment)
in #1680.

If lines had been deleted from a file, and it was not yet saved when user
then toggled a line near the end of the file in Reading mode,
there could be an access of a non-existent line number.

The resultant output was:

plugin:obsidian-tasks-plugin:17605 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'includes')
    at eval (plugin:obsidian-tasks-plugin:17605:14)
    at Generator.next (<anonymous>)
    at fulfilled (plugin:obsidian-tasks-plugin:173:24)
claremacrae added a commit that referenced this issue Mar 20, 2023
…1772)

* fix: Prevent exception if toggling in unsaved file in Reading mode

See the comment #1680 (comment)
in #1680.

If lines had been deleted from a file, and it was not yet saved when user
then toggled a line near the end of the file in Reading mode,
there could be an access of a non-existent line number.

The resultant output was:

plugin:obsidian-tasks-plugin:17605 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'includes')
    at eval (plugin:obsidian-tasks-plugin:17605:14)
    at Generator.next (<anonymous>)
    at fulfilled (plugin:obsidian-tasks-plugin:173:24)

* comment: Fix typo
@claremacrae claremacrae moved this from 🔖 Ready to 🏗 In progress in Tasks (Obsidian plugin) Roadmap Mar 21, 2023
claremacrae added a commit that referenced this issue Mar 21, 2023
This fixes #688, in the usual case where the embedded task
only appears once in the file.

It also fixes #1680, again so long as the task line only
appears once in the file.
claremacrae added a commit that referenced this issue Mar 21, 2023
1680 - Reading Mode line numbers not updated on editing.md
@github-project-automation github-project-automation bot moved this from 🏗 In progress to ✅ Done in Tasks (Obsidian plugin) Roadmap Mar 21, 2023
@claremacrae claremacrae moved this from ✅ Done to 🎉 Released in Tasks (Obsidian plugin) Roadmap Mar 22, 2023
@claremacrae claremacrae added the display: reading mode Issues referring to Obsidian's Reading Mode or Reading View label Oct 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
display: reading mode Issues referring to Obsidian's Reading Mode or Reading View scope: data integrity Problems that may cause loss or corruption of user data type: bug Something isn't working
Projects
Status: 🎉 Released
Development

Successfully merging a pull request may close this issue.

2 participants