-
Notifications
You must be signed in to change notification settings - Fork 156
Handlers for external changes or deletions #121
Conversation
…ted by another program.
…pen the proper dialog box if the app is closed without saving.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your contribution!
I added some comments and questions for you in the diff.
@@ -412,7 +478,7 @@ AbrDocument.prototype = { | |||
autopreview: function (types, lines) { | |||
var cm = this.cm; | |||
types = types || this.autopreviewTypes; | |||
if (lines == null) { | |||
if (lines === null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't change == null
. This is why .jshintrc includes the "eqnull": true
rule.
See http://marijnhaverbeke.nl/blog/null-and-undefined.html for an explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation! I'll rollback the change.
noLink: true | ||
}); | ||
}, | ||
|
||
warnFileDeleted: function (path, callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather display a warning here: "The file [path] doesn't exist anymore. Keep this file in editor?" => Yes/No
"No" closes the file, "Yes" sets the doc dirty and keep it open.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense.
I actually hesitated between these two behaviors.
Sublime Text simply changes the document state to "dirty", but Notepad++ does what you are describing.
@@ -257,6 +262,10 @@ AbrDocument.prototype = { | |||
this.latestGeneration = this.getGeneration(); | |||
}, | |||
|
|||
setDirty: function () { | |||
this.latestGeneration--; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can lead to unexpected behaviors, especially when going back in the undo stack. I would rather set this.latestGeneration
to -1
in order to be sure its value will never be the equal to CodeMirror's changeGeneration()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
The intention with -1
would also be clearer; decrementing latestGeneration
looks like we are stepping back in the undo/redo stack when it's not the case.
@@ -350,6 +366,65 @@ AbrDocument.prototype = { | |||
return this.save(path, callback); | |||
}, | |||
|
|||
initWatcher: function () { | |||
var that = this; | |||
var updateTitle = function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you simply use abrDoc.updateWindowTitle()
here instead of creating a duplicate of it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I understand, updateWindowTitle()
relies on CodeMirror's state to declare the document as clean or not.
Technically, since the document didn't change in CodeMirror, calling updateWindowTitle()
here would not add the *
symbol in the window title. I didn't try it though, so I might be wrong.
In the end, I just wanted a visual feedback for the user without forcing CodeMirror to change its state (maybe with side effects I don't know about).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, since the document didn't change in CodeMirror, calling updateWindowTitle() here would not add the * symbol in the window title. I didn't try it though, so I might be wrong.
If you call setDirty()
before updateWindowTitle()
this should work.
I just wanted a visual feedback for the user without forcing CodeMirror to change its state
CodeMirror state won't change. Actually isClean()
is just asking CodeMirror if latestGeneration
matches the current state, so if you set latestGeneration
to -1 this would do the job.
If you don't mind I'd prefer to keep this function (update title) unique, so if we need to change it in the future we won't forget anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I see.
I didn't really like it anyway, because having two functions like that can be confusing.
I'll remove the duplicate function.
unlink: function (path) { | ||
dialogs.warnFileDeleted(path, function () { | ||
that.setDirty(); | ||
updateTitle(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my previous comment:
that.setDirty();
that.updateWindowTitle();
} | ||
that.startWatcher(); | ||
}); | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this return value is useless because the function is async (but it was always the case in my code, my mistake)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only added it to stay consistent with saveAs()
.
Since it can return false
if it gets an invalid path, I thought it should return true
if the function doesn't exit early.
Should we get rid of these boolean returns altogether?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense, let's keep it like this.
return files.writeFile(data, path, callback2); | ||
data = this.getData(); | ||
files.writeFile(data, path, function (err) { | ||
if (err) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe that.startWatcher();
should be triggered here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure which part you are referring to.
that.startWatcher()
is triggered at the end of files.writeFile()
's callback, because the path should be updated before (done by that.setPath(path)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a return
in the if (err)
block. If this block is reached and the user cancels the "Save As" dialog then the watcher won't be started again.
I would do something like:
if (err) {
that.startWatcher()
return dialogs.fileAccessDenied(path, function () {
that.saveAs(callback);
});
}
Or maybe I'm missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right; the watcher won't restart if the user cancels here.
But it looks weird if I just call that.startWatcher()
twice in the same function.
It would be clearer if dialogs.fileAccessDenied()
can return the user's choice instead of always false
.
Then the additional call to that.startWatcher()
would be justified in the code.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it looks weird if I just call that.startWatcher() twice in the same function.
Actually it is not really called twice because of the return
in the if (err)
block. I see this block more like an separate error handler.
It would be clearer if dialogs.fileAccessDenied() can return the user's choice instead of always false.
Well, I don't really agree with this. In my opinion userChoice
should remains internal to dialog functions (because we can exchange buttons position, which would change the returned index, by instance).
And in my opinion the watcher should be started before the "Save As" dialog is opened in order to clearly isolate the different operations (save, then save as).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damn, I thought the additional call was before the if (err)
block, sorry about that 😅
It totally makes sense then.
@@ -31,7 +31,7 @@ var appDialogs = { | |||
options = { | |||
title: "About", | |||
message: "ABRICOTINE - MARKDOWN EDITOR (v. " + constants.appVersion + ")\n\nCopyright (c) 2015 Thomas Brouard\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.", | |||
buttons: ['OK'], | |||
buttons: ["OK"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Next time could you correct coding style issues in a separate commit please? This would make the diff easier to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, sorry about that 😞
I'll be more careful next time.
Thanks for taking the time to review this 👍 |
…e file in the editor, instead of just displaying a warning.
…tement in case of access denied on save.
The behavior here is a bit odd. I'm getting two popups when the file is modified by another process, one after another, both saying the same thing. One is a dialog box (i.e. separate window) and one is a sheet (i.e. in the main window). If I say "Yes" to either (or both), the file is reloaded. When deleting a file, I'm getting one dialog immediately, saying the file no longer exists. This seems to be working as intended. However, on every subsequent save of the file, after the file is written to disk, Abricotine shows a sheet saying that another process has modified the file (identical to the second screenshot above) and asking to reload. Also, I occasionally get the following error when a file is modified by another process or saved by Abricotine: It seems to happen randomly, and I can't reproduce it reliably. Once it happens once, it happens every time I try to save the file, and I have to close the window and re-open to get rid of it. |
@nloveladyallen The first issue occurs when multiple changes are made by another process, I'll prepare a fix. The second one should not happen anymore with the latest commits. |
…uring file reload dialogs to avoid multiple spawns.
@darahak Are you still working on this PR or is it ready to merge? |
I'm not working on it anymore. |
👍 |
Proposal for #94
With this patch, Abricotine will display message boxes if the current file has been modified or deleted by a third party.
On changes, the user can choose to reload the file or leave it untouched.
In this case, or if the file was deleted, the document is forced to a "dirty" state to allow save, even on exit.
(Sorry about the noise with all the double quote changes)