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

fix: Prevent rare overwrite of wrong line in Reading mode & results blocks #1663

Merged
merged 2 commits into from Feb 16, 2023

Conversation

BluBloos
Copy link
Contributor

@BluBloos BluBloos commented Feb 14, 2023

Description

In essence, when a task is toggled by the Tasks plugin, the code reads the file that contains the task to find the line to update.

Under certain circumstances (listed below) it may find and update the wrong line, overwriting a different task.

This change makes Tasks check, when in Reading mode and in Tasks results blocks, that the line it is about to overwrite actually contains the expected text. If it does not, the toggling action will halt, and a message will be written to the console.

Note: This change does not affect Live Preview. That is planned to be addressed in a separate pull request.


All of the detail needed should be in the commit messages.

Update: The key bit is:

Currently, the unique identifier (UID) for tasks
are a source file, a section, and an index into
the array of global-filter-matching tasks within
that section.

This diff does not change how tasks are uniquely
identified.

However, it does implement a guard when overwriting a
task in some file to ensure the line that is being
overwritten has similar content to the line that
was used to create the Task in the first place.

The world "similar" is used since the line is
permitted to have different content, so long as that
line represents a Task with identical data as per
the definition of Task.identicalTo

Motivation and Context

As an interim fix to #688 so that the users' data is not overwritten.

Some additional notes:

  • should fix very occasional accidental overwriting of the wrong line when toggling a task - suspected to maybe be caused by file being synced whilst the task is being completed...
  • may fix Lists of Checkboxes are corrupted when accessed from Canvas #1659, but need more info on repro case from reporter...
  • once the checkbox is clicked and this guard is "hit", the checkbox cannot be clicked again. This wasn't intentional but it happens...
  • also fixes (disables incorrect data overwrite) inline markdown blocks that reference a header of a note.
    ex)
## Category1 
- [ ] task1

![[#Category 2]]

## Category 2
- [ ] task2

How has this been tested?

Locally and within my personal vault.

I've tested the fix for the particular issue plus normal usage.

Screenshots (if appropriate)

Screen.Recording.2023-02-14.at.8.02.42.AM.mov

Types of changes

Changes visible to users:

  • Bug fix (prefix: fix - non-breaking change which fixes an issue)
  • New feature (prefix: feat - non-breaking change which adds functionality)
  • Breaking change (prefix: feat!! or fix!! - fix or feature that would cause existing functionality to not work as expected)
  • Documentation (prefix: docs - improvements to any documentation content)
  • Sample vault (prefix: vault - improvements to the Tasks-Demo sample vault)

Internal changes:

  • Refactor (prefix: refactor - non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
  • Tests (prefix: test - additions and improvements to unit tests and the smoke tests)
  • Infrastructure (prefix: chore - examples include GitHub Actions, issue templates)

Checklist

Terms

Copy link
Collaborator

@claremacrae claremacrae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. The change is a lot simpler than I expected.

I'm curious why you used identicalTo and not equality check of the original source line?
Both are fine - I'm just wondering what you spotted that I didn't! 😄

src/File.ts Outdated Show resolved Hide resolved
src/File.ts Outdated Show resolved Hide resolved
src/File.ts Outdated Show resolved Hide resolved
src/File.ts Show resolved Hide resolved
Currently, the unique identifier (UID) for tasks
are a source file, a section, and an index into
the array of global-filter-matching tasks within
that section.

This diff does not change how tasks are uniquely
identified.

However, it does implement a guard when overwriting a
task in some file to ensure the line that is being
overwritten has similar content to the line that
was used to create the Task in the first place.

The world "similar" is used since the line is
permitted to have different content, so long as that
line represents a Task with identical data as per
the definition of Task.identicalTo
@BluBloos BluBloos changed the title Fix for data integrity Fix for data integrity when using replaceTaskWithTasks Feb 15, 2023
@BluBloos
Copy link
Contributor Author

I'm curious why you used identicalTo and not equality check of the original source line?

Currently, the permissible format for a task on a line of the users' source file is different than what we render.
So we can take a line, parse to Task, render to line, and this final line will be different than the original.

Therefore, comparing the Tasks is the way to go!

Screen.Recording.2023-02-14.at.10.47.43.PM.mov

@claremacrae claremacrae changed the title Fix for data integrity when using replaceTaskWithTasks fix: Prevent rare overwrite of wrong line in Reading mode & results blocks Feb 15, 2023
Comment on lines +140 to +143
path: originalTask.path,
precedingHeader: originalTask.precedingHeader,
sectionStart: originalTask.sectionStart,
sectionIndex: originalTask.sectionIndex,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to leave this as it is, but I feel that because these checks use values from the original task instead of the new line, they are very weak and do not add any value to the checking for whether the two task lines match.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using these fields from originalTask makes sense.

we locate the line with path, sectionStart, and sectionIndex, therefore those are part of the new Task that gets created from the line. The only cause for concern here is precedingHeader as that may change in the interim.

is there something you're saying that I'm not understanding?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there something you're saying that I'm not understanding?

Maybe, or maybe vice versa. Maybe I'm not understanding. And unfortunately the earlier conversations are all hidden now, by being resolved.

I thought that the idea behind calling task.identicalTo() was to check more fields in the two tasks, but it looks to me like one of the tasks is populated from fields from the other task.

So it looks to me like task.identicalTo() is giving a false sense of extra value - and it would probably be safer and clearer to check task.originalMarkdown.

I may later refactor the new code out to a helper function in a file that does not import Obsidian types, to be able to write some tests for it, and explore the behaviour better.

The only cause for concern here is precedingHeader as that may change in the interim.

Yes, that is a concern.

Another concern is a change behind the scenes somehow from this:

## header

- [ ] task 1
- [ ] task 2 - I am the task that will be completed

to this:

## header

- [ ] task 1
- [ ] some new task that was added - so the old 2nd task is now the 3rd task in the section
- [ ] task 2 - I am the task that will be completed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you read my comment here: #1663 (comment)?

it contains the answer to the purpose of task.identitcalTo

I could elaborate more if needed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I did see that. I should have said that I don’t understand why the rendering of the task matters.

The line being overwritten should be identical to the original text read in to the task being edited.

If the two markdown lines differ in any way, including order of fields, something has gone wrong as the file has been edited in some way behind Tasks’ back, and I do not feel it is safe to overwrite the line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Yes, in that case .originalMarkdown is the way to go. I was being lenient with my change and allowing even for the order of the fields to change. I think a factor at play was that I sort of forgot that we had .originalMarkdown 😅

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @BluBloos Thank you, and I totally understand... I'll change it to use .originalMarkdown when I tweak the error message...

Copy link
Collaborator

@claremacrae claremacrae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much!

@claremacrae claremacrae merged commit ae45bb2 into obsidian-tasks-group:main Feb 16, 2023
@claremacrae
Copy link
Collaborator

Aagghghh.
Remember I said I had tried and failed to eliminate a bunch of duplication.
In doing some more testing on this, I just discovered that the ToggleDone code has a whole other set of similar code.... 😢

const regexMatch = line.match(TaskRegularExpressions.taskRegex);
if (regexMatch !== null) {
// Toggle the status of the checklist item.
const statusString = regexMatch[3];
const status = StatusRegistry.getInstance().bySymbol(statusString);
const newStatusString = status.nextStatusSymbol;
toggledLine = line.replace(TaskRegularExpressions.taskRegex, `$1- [${newStatusString}] $4`);
} else if (TaskRegularExpressions.listItemRegex.test(line)) {
// Convert the list item to a checklist item.
toggledLine = line.replace(TaskRegularExpressions.listItemRegex, '$1$2 [ ]');
} else {
// Convert the line to a list item.
toggledLine = line.replace(TaskRegularExpressions.indentationRegex, '$1- ');
}
}

@BluBloos
Copy link
Contributor Author

What's the concern with the code you referenced? It's not really clear to me ...

@claremacrae
Copy link
Collaborator

What's the concern with the code you referenced? It's not really clear to me ...

Ah, sorry, I may have shortened it down too much, maybe more context from the surrounding code is needed...

It is in a function that looks to me like it is over-writing task lines in files without checking whether the correct line is being overwritten.

@BluBloos
Copy link
Contributor Author

It is in a function that looks to me like it is over-writing task lines in files without checking whether the correct line is being overwritten.

I see. in this case I think checking if the correct line is being overwritten is not-so-relevant as toggleDone is hooking into the editor callback. The callback just modifies the line where the users' cursor is. So it's always going to be on the task.

@claremacrae
Copy link
Collaborator

The callback just modifies the line where the users' cursor is. So it's always going to be on the task.

Brilliant. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lists of Checkboxes are corrupted when accessed from Canvas
2 participants