-
-
Notifications
You must be signed in to change notification settings - Fork 109
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
bug: Watch mode only works once #84
Comments
Thanks, from what I can see watch mode works fine though. If you close and re-open the PDF, it should show the correct content (at least it does for me). I'm on MacOS so I'm using Preview to view the PDF, and I have the same problem that it doesn't refresh anymore after the first change. Then I close the preview and re-open it, and then the refresh works again but only once. I'll look into whether I'll have to close the file after each write. Assuming it has something to do with that... probably without closing it, it won't update the created-at/updated-at timestamp of the file until the process ends. |
I've tried a few things and I think it's really an issue of the software you use to view the PDF. I've looked into Node's So I tried a different app (Skim) that supports auto-reload on file changes to preview the app, and that does reload after every change. Not sure what PDF viewer there is for Windows that supports this as well but I'm sure you'll find one (: |
Hello @simonhaenisch,
In the GIF, it is hard/impossible to see but I can confirm that even when the PDF is generated/shown empty Sumatra does trigger a refresh (there is a quick flickering probably removed by the GIF converter because too quick...). For me the file is "really" not generated/written correctly. If I reopen, it is still empty.
For me, after each change the viewer is refreshing correctly the file when doing it manually.
I am using SumatraPDF which support hot reload. At least, it was the first result when I did a google search on a PDF reader that doesn't block the An easier way to visualise the problem, is by using the Note: When I am not recording the watch mode works always once only. When recording it seems like it was more random perhaps due to my computer recording my screen (so being slower?) After, the GIF I tried 5 more times without the output being correct. I removed thuse try from the GIF as it was already long enough and didn't bring value. |
Ok thanks for the example with the html output, that's very strange. I'll investigate and see whether I can reproduce it. |
Hm I just tried this a lot of times with HTML output and I can't reproduce it. You're saying that it happens more often when your pc is faster, so I've checked the source for race conditions but I can't see anything that looks wrong 🤷🏻♂️ I can't really do anything at this point, sorry. If you're keen to jump into the source and add some |
No problem, I understand that it is hard to fix a bug you can't reproduce.
Ahah, I was actually trying to reproduce it on my computer at home and toying with the code here are my discovery so far. Reproducing the problem on my computer was really reliable with my reproduction step from above. One way, I found to reproduce it was by triggering "several" changes in a short period of time. Basically, smashing I am using VSCode and it seems like even if the file content didn't change it trigger the change event of I added some logs to show the end of the entry file and the end of the generated content. I also added a custom check to make the process crash when the generated content was not the one expected. My test markdown file looks this (space on the first and last line): # Test
## Test2
<!-- TOC -->
- [Test](#test)
- [Test2](#test2)
<!-- /TOC -->
Some content
Diff Here is a case where the error occurred: As you can see, it seems like it didn't generate the expected output because the entry content was empty. However, like I said before I was just pressing After that discovery I wanted to check the file information to see if VSCode was recreating a new file each time or something. So I made this changes, to get back the However, with that extra code it was much harder to make the program crash. This leads me to this question: Is it possible that, it is reading the file content too quickly when the Out of curiosity, I also decided to see what happened if I was doing the same thing using Notepad (the basic text editor included on Windows). Well... even by spamming or keeping Tomorrow, I will try to redo my tests on my computer at work to see the behaviour. See if using Sorry for the really long comment, I hope that I was more or less clear in my explanations (a lot was going on) 😅 |
Thanks for all the details. I actually tried saving the file twice so that two "generating" actions would race with each other and it was still working as expected for me (two reloads, both had the same content). I didn't think about how VS Code writes the file, so that's some good discoveries there. It's possible that this is platform-specific. I did some searching for "VS Code atomic saves" and found this old node-sass issue, specifically this comment: sass/node-sass#1894 (comment). I think it's pretty much the same issue: chokidar already picks up the file change before VS Code has finished closing it, so when md-to-pdf reads it it's still empty. It's hard to tell who is to blame: VS Code could use atomic saves, then this wouldn't happen; Since the latter is the only thing I can do about it, i'll look into doing that. |
Hello @simonhaenisch, I just tested on my laptop at work and when using Notepad instead of VSCode, I am not able to make the process crash using the It seems to confirm that this is a problem with how VSCode saves the files as you discovered. I tried to see if the snippet shared in the discussion you linked sass/node-sass#1894 (comment) could improve the situation. Using a if (args['--watch']) {
console.log(chalk.bgBlue('\n watching for changes \n'));
- new Listr([getListrTask(file)], { exitOnError: false }).run().catch(console.error),
+ watch(files).on('change', async (file) => {
+ setTimeout(() => {
+ new Listr([getListrTask(file)], { exitOnError: false }).run().catch(console.error)
+ }, 100)
+ }
+ );
} else {
server.close(); It is still not perfect because I can still make the process crash if I spam If I test it in a standard workflow using
From what I understand, it seems like this solution should be more reliable than the For now, I will use my fork with |
Ok thanks a lot for all the collaboration, I'll look into this now! |
@MangelMaxime I've created a branch wait-for-file, do you think you could test it out? Basically what it's doing is it's trying to open the file for writing first. If the file still has an open handle from another process, then this should throw an |
@simonhaenisch This looks really promising but... yes there is a but 😅 If I am using VSCode without any markdown extension, your solution seems to works 95% of the time when using it in a standard workflow (not spamming To make it a bit more consistent I would increase the Even, if the file can be generated "empty" if user did 2 quick saves by mistakes, re-saving generates the file correctly. Here comes the but: If I am using VSCode with an extension which for example update the Table of Content or something like that it will trigger 2 really quick saves all the times.
In this scenario, sometimes the generated file is ok sometimes not; lets say it is a 50/50 chances. I think that what is happening is:
I am using the AlanWalk.markdown-toc extensions which is using So there is probably a way to fix this extension in particular. So for me we are at this point right now: We consider that the an extension (from any editor) should always intercept the save command and modify the file content to have a single change trigger. In this case, your current fix seems to be working with just my proposition of extending the delay a bit more. We consider that the problem I have with the extension can happen for another extensions or editor because it doesn't have the API to intercept a save command and then we want to have a solution for that too. In that case, I think adding a "Debouncer" should do the trick as it would aggregate really quick changes into a single change. For example, Webpack, Nodemon, does that and expose the delay parameter so the user can customize it depending on its needs. As a side note, yesterday was the first time I was able to use |
Yeah I agree that ideally extensions would not trigger additional file writes but hook into the save like a post-processor instead. I also want to make it easier to hook extensions into Anyway, I checked the Line 147 in 35f314c
to watch(files, { awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 } }).on('change', async (file) => (feel free to play with the values but I think the default threshold of TBH I might just expose the whole |
I just tested using Here is the code I used, which removed the custom if (args['--watch']) {
console.log(chalk.bgBlue('\n watching for changes \n'));
// const waitForFile = async (path: string) => {
// let handle: fs.FileHandle | void;
// // eslint-disable-next-line no-await-in-loop
// while (!(handle = await fs.open(path, 'r+').catch(throwIfNotBusy))) {
// await new Promise((resolve) => setTimeout(resolve, 5)); // eslint-disable-line no-await-in-loop
// }
// await handle.close();
// };
// const throwIfNotBusy = (error: any) => {
// if (error.code !== 'EBUSY') {
// throw error;
// }
// };
watch(files, { awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 } }).on('change', async (file) => {
// await waitForFile(file);
return new Listr([getListrTask(file)], { exitOnError: false }).run().catch(console.error);
});
} else {
server.close();
}
}) With this new option if I spam
Exposing the options makes sense to me. Indeed I would probably set the value to It seems like we have a stable solution to the different problems 👍
Yep it is already kind of possible to customise const marked = require("marked");
const hjs = require('highlight.js');
const renderer = new marked.Renderer();
renderer.code = (code, language) => {
if (language === "mermaid") {
return '<div class="mermaid">' + code + '</div>';
} else {
// if the given language is not available in highlight.js, fall back to plaintext
const languageName = language && hjs.getLanguage(language) ? language : 'plaintext';
return `<pre><code class="hljs ${languageName}">${hjs.highlight(languageName, code).value}</code></pre>`;
}
};
module.exports = {
marked_options: {
headerIds: false,
smartypants: true,
renderer: renderer
}, Also, the benefit of using an extension in my case is that the TOC is written in the markdown file so I can read the TOC from it directly and click on the link while editing my file. If I was using marked the TOC would not exist in the markdown file only in the output. Both have pros and cons and I suppose it depends on the usage made or habits. |
I don't think I'll provide a default value here because I can't even reproduce the issue at all on macOS so it's probably not even necessary for a good chunk of the users. But thanks for confirming that this fixes it, i'll see that I make a commit and release this soon. |
Just released 3.3.0 which has the new |
Thank you @simonhaenisch for your help on this problem. I will do the upgrade tomorrow at work |
Context:
md-to-pdf -v
): 3.2.1Describe the bug:
The watch mode only works the first time after it generates an empty PDF.
A clear and concise description of what the bug is. Feel free to include screenshots.
documentation.md
filemd2pdf documentation.md --watch
documentation.md
documentation.md
The problem occurs also when adding
--as-html
.The text was updated successfully, but these errors were encountered: