Fix file can be marked as saved even it's been deleted outside

This PR make buffer always dirty (with any undo/redo operation) if the editing buffer is unsyncronized with file on disk.
By "unsyncronized", it means:
1. the file is deleted outside but the buffer in Notepad++ is kept.
2. the file is modified by another app but the buffer is not reloaded in Notepad++.

Note that if the buffer is untitled, there's no correspondent file on the disk so the buffer is considered as independent therefore synchronized.

Fix #10401, close #10616
This commit is contained in:
Don Ho 2021-10-04 03:16:34 +02:00
parent 97ad1d922e
commit 6c3031f01b
4 changed files with 60 additions and 90 deletions

View File

@ -5874,11 +5874,18 @@ void Notepad_plus::notifyBufferChanged(Buffer * buffer, int mask)
// Since the file content has changed but the user doesn't want to reload it, set state to dirty // Since the file content has changed but the user doesn't want to reload it, set state to dirty
buffer->setDirty(true); buffer->setDirty(true);
// buffer in Notepad++ is not syncronized anymore with the file on disk
buffer->setUnsync(true);
break; //abort break; //abort
} }
} }
// Set _isLoadedDirty false so when the document clean state is reached the icon will be set to blue // Set _isLoadedDirty false so when the document clean state is reached the icon will be set to blue
buffer->setLoadedDirty(false); buffer->setLoadedDirty(false);
// buffer in Notepad++ is syncronized with the file on disk
buffer->setUnsync(false);
doReload(buffer->getID(), false); doReload(buffer->getID(), false);
if (mainActive || subActive) if (mainActive || subActive)
{ {
@ -5924,6 +5931,12 @@ void Notepad_plus::notifyBufferChanged(Buffer * buffer, int mask)
doClose(buffer->getID(), currentView(), isSnapshotMode); doClose(buffer->getID(), currentView(), isSnapshotMode);
return; return;
} }
else
{
// buffer in Notepad++ is not syncronized anymore with the file on disk
buffer->setUnsync(true);
}
break; break;
} }
} }

View File

@ -127,6 +127,10 @@ BOOL Notepad_plus::notify(SCNotification *notification)
if (!canUndo && buf->isLoadedDirty() && buf->isDirty()) if (!canUndo && buf->isLoadedDirty() && buf->isDirty())
isDirty = true; isDirty = true;
} }
if (buf->isUnsync()) // buffer in Notepad++ is not syncronized with the file on disk - in this case the buffer is always dirty
isDirty = true;
buf->setDirty(isDirty); buf->setDirty(isDirty);
break; break;
} }

View File

@ -1050,6 +1050,7 @@ SavingStatus FileManager::saveBuffer(BufferID id, const TCHAR * filename, bool i
buffer->setFileName(fullpath, language); buffer->setFileName(fullpath, language);
buffer->setDirty(false); buffer->setDirty(false);
buffer->setUnsync(false);
buffer->setStatus(DOC_REGULAR); buffer->setStatus(DOC_REGULAR);
buffer->checkFileState(); buffer->checkFileState();
_pscratchTilla->execute(SCI_SETSAVEPOINT); _pscratchTilla->execute(SCI_SETSAVEPOINT);

View File

@ -27,8 +27,7 @@ typedef Buffer* BufferID; //each buffer has unique ID by which it can be retriev
typedef sptr_t Document; typedef sptr_t Document;
enum DocFileStatus enum DocFileStatus {
{
DOC_REGULAR = 0x01, // should not be combined with anything DOC_REGULAR = 0x01, // should not be combined with anything
DOC_UNNAMED = 0x02, // not saved (new ##) DOC_UNNAMED = 0x02, // not saved (new ##)
DOC_DELETED = 0x04, // doesn't exist in environment anymore, but not DOC_UNNAMED DOC_DELETED = 0x04, // doesn't exist in environment anymore, but not DOC_UNNAMED
@ -36,8 +35,7 @@ enum DocFileStatus
DOC_NEEDRELOAD = 0x10 // File is modified & needed to be reload (by log monitoring) DOC_NEEDRELOAD = 0x10 // File is modified & needed to be reload (by log monitoring)
}; };
enum BufferStatusInfo enum BufferStatusInfo {
{
BufferChangeLanguage = 0x001, // Language was altered BufferChangeLanguage = 0x001, // Language was altered
BufferChangeDirty = 0x002, // Buffer has changed dirty state BufferChangeDirty = 0x002, // Buffer has changed dirty state
BufferChangeFormat = 0x004, // EOL type was changed BufferChangeFormat = 0x004, // EOL type was changed
@ -51,8 +49,7 @@ enum BufferStatusInfo
BufferChangeMask = 0x3FF // Mask: covers all changes BufferChangeMask = 0x3FF // Mask: covers all changes
}; };
enum SavingStatus enum SavingStatus {
{
SaveOK = 0, SaveOK = 0,
SaveOpenFailed = 1, SaveOpenFailed = 1,
SaveWrittingFailed = 2 SaveWrittingFailed = 2
@ -61,8 +58,7 @@ enum SavingStatus
const TCHAR UNTITLED_STR[] = TEXT("new "); const TCHAR UNTITLED_STR[] = TEXT("new ");
//File manager class maintains all buffers //File manager class maintains all buffers
class FileManager final class FileManager final {
{
public: public:
void init(Notepad_plus* pNotepadPlus, ScintillaEditView* pscratchTilla); void init(Notepad_plus* pNotepadPlus, ScintillaEditView* pscratchTilla);
@ -108,7 +104,6 @@ public:
int docLength(Buffer * buffer) const; int docLength(Buffer * buffer) const;
size_t nextUntitledNewNumber() const; size_t nextUntitledNewNumber() const;
private: private:
struct LoadedFileFormat { struct LoadedFileFormat {
LoadedFileFormat() = default; LoadedFileFormat() = default;
@ -144,8 +139,7 @@ private:
#define MainFileManager FileManager::getInstance() #define MainFileManager FileManager::getInstance()
class Buffer final class Buffer final {
{
friend class FileManager; friend class FileManager;
public: public:
//Loading a document: //Loading a document:
@ -162,9 +156,7 @@ public:
// 3. gets the last modified time // 3. gets the last modified time
void setFileName(const TCHAR *fn, LangType defaultLang = L_TEXT); void setFileName(const TCHAR *fn, LangType defaultLang = L_TEXT);
const TCHAR * getFullPathName() const { const TCHAR * getFullPathName() const { return _fullPathName.c_str(); }
return _fullPathName.c_str();
}
const TCHAR * getFileName() const { return _fileName; } const TCHAR * getFileName() const { return _fileName; }
@ -179,70 +171,48 @@ public:
bool checkFileState(); bool checkFileState();
bool isDirty() const { bool isDirty() const { return _isDirty; }
return _isDirty;
}
bool isReadOnly() const { bool isReadOnly() const { return (_isUserReadOnly || _isFileReadOnly); };
return (_isUserReadOnly || _isFileReadOnly);
};
bool isUntitled() const { bool isUntitled() const { return (_currentStatus == DOC_UNNAMED); }
return (_currentStatus == DOC_UNNAMED);
}
bool getFileReadOnly() const { bool getFileReadOnly() const { return _isFileReadOnly; }
return _isFileReadOnly;
}
void setFileReadOnly(bool ro) { void setFileReadOnly(bool ro) {
_isFileReadOnly = ro; _isFileReadOnly = ro;
doNotify(BufferChangeReadonly); doNotify(BufferChangeReadonly);
} }
bool getUserReadOnly() const { bool getUserReadOnly() const { return _isUserReadOnly; }
return _isUserReadOnly;
}
void setUserReadOnly(bool ro) { void setUserReadOnly(bool ro) {
_isUserReadOnly = ro; _isUserReadOnly = ro;
doNotify(BufferChangeReadonly); doNotify(BufferChangeReadonly);
} }
EolType getEolFormat() const { EolType getEolFormat() const { return _eolFormat; }
return _eolFormat;
}
void setEolFormat(EolType format) { void setEolFormat(EolType format) {
_eolFormat = format; _eolFormat = format;
doNotify(BufferChangeFormat); doNotify(BufferChangeFormat);
} }
LangType getLangType() const { LangType getLangType() const { return _lang; }
return _lang;
}
void setLangType(LangType lang, const TCHAR * userLangName = TEXT("")); void setLangType(LangType lang, const TCHAR * userLangName = TEXT(""));
UniMode getUnicodeMode() const { UniMode getUnicodeMode() const { return _unicodeMode; }
return _unicodeMode;
}
void setUnicodeMode(UniMode mode); void setUnicodeMode(UniMode mode);
int getEncoding() const { int getEncoding() const { return _encoding; }
return _encoding;
}
void setEncoding(int encoding); void setEncoding(int encoding);
DocFileStatus getStatus() const { DocFileStatus getStatus() const { return _currentStatus; }
return _currentStatus;
}
Document getDocument() { Document getDocument() { return _doc; }
return _doc;
}
void setDirty(bool dirty); void setDirty(bool dirty);
@ -252,47 +222,34 @@ public:
void setHeaderLineState(const std::vector<size_t> & folds, ScintillaEditView * identifier); void setHeaderLineState(const std::vector<size_t> & folds, ScintillaEditView * identifier);
const std::vector<size_t> & getHeaderLineState(const ScintillaEditView * identifier) const; const std::vector<size_t> & getHeaderLineState(const ScintillaEditView * identifier) const;
bool isUserDefineLangExt() const bool isUserDefineLangExt() const { return (_userLangExt[0] != '\0'); }
{
return (_userLangExt[0] != '\0');
}
const TCHAR * getUserDefineLangName() const const TCHAR * getUserDefineLangName() const { return _userLangExt.c_str(); }
{
return _userLangExt.c_str();
}
const TCHAR * getCommentLineSymbol() const const TCHAR * getCommentLineSymbol() const {
{
Lang *l = getCurrentLang(); Lang *l = getCurrentLang();
if (!l) if (!l)
return NULL; return NULL;
return l->_pCommentLineSymbol; return l->_pCommentLineSymbol;
} }
const TCHAR * getCommentStart() const const TCHAR * getCommentStart() const {
{
Lang *l = getCurrentLang(); Lang *l = getCurrentLang();
if (!l) if (!l)
return NULL; return NULL;
return l->_pCommentStart; return l->_pCommentStart;
} }
const TCHAR * getCommentEnd() const const TCHAR * getCommentEnd() const {
{
Lang *l = getCurrentLang(); Lang *l = getCurrentLang();
if (!l) if (!l)
return NULL; return NULL;
return l->_pCommentEnd; return l->_pCommentEnd;
} }
bool getNeedsLexing() const bool getNeedsLexing() const { return _needLexer; }
{
return _needLexer;
}
void setNeedsLexing(bool lex) void setNeedsLexing(bool lex) {
{
_needLexer = lex; _needLexer = lex;
doNotify(BufferChangeLexing); doNotify(BufferChangeLexing);
} }
@ -305,18 +262,10 @@ public:
void setDeferredReload(); void setDeferredReload();
bool getNeedReload() bool getNeedReload() const { return _needReloading; }
{ void setNeedReload(bool reload) { _needReloading = reload; }
return _needReloading;
}
void setNeedReload(bool reload) int docLength() const {
{
_needReloading = reload;
}
int docLength() const
{
assert(_pManager != nullptr); assert(_pManager != nullptr);
return _pManager->docLength(_id); return _pManager->docLength(_id);
} }
@ -330,28 +279,24 @@ public:
bool isModified() const { return _isModified; } bool isModified() const { return _isModified; }
void setModifiedStatus(bool isModified) { _isModified = isModified; } void setModifiedStatus(bool isModified) { _isModified = isModified; }
generic_string getBackupFileName() const { return _backupFileName; } generic_string getBackupFileName() const { return _backupFileName; }
void setBackupFileName(const generic_string& fileName) { _backupFileName = fileName; } void setBackupFileName(const generic_string& fileName) { _backupFileName = fileName; }
FILETIME getLastModifiedTimestamp() const { return _timeStamp; } FILETIME getLastModifiedTimestamp() const { return _timeStamp; }
bool isLoadedDirty() const bool isLoadedDirty() const { return _isLoadedDirty; }
{ void setLoadedDirty(bool val) { _isLoadedDirty = val; }
return _isLoadedDirty;
}
void setLoadedDirty(bool val) bool isUnsync() const { return _isUnsync; }
{ void setUnsync(bool val) { _isUnsync = val; }
_isLoadedDirty = val;
}
void startMonitoring() { void startMonitoring() {
_isMonitoringOn = true; _isMonitoringOn = true;
_eventHandle = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); _eventHandle = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
}; };
HANDLE getMonitoringEvent() const { HANDLE getMonitoringEvent() const { return _eventHandle; };
return _eventHandle;
};
void stopMonitoring() { void stopMonitoring() {
_isMonitoringOn = false; _isMonitoringOn = false;
@ -420,6 +365,13 @@ private:
bool _isModified = false; bool _isModified = false;
bool _isLoadedDirty = false; // it's the indicator for finding buffer's initial state bool _isLoadedDirty = false; // it's the indicator for finding buffer's initial state
bool _isUnsync = false; // Buffer should be always dirty (with any undo/redo operation) if the editing buffer is unsyncronized with file on disk.
// By "unsyncronized" it means :
// 1. the file is deleted outside but the buffer in Notepad++ is kept.
// 2. the file is modified by another app but the buffer is not reloaded in Notepad++.
// Note that if the buffer is untitled, there's no correspondent file on the disk so the buffer is considered as independent therefore synchronized.
// For the monitoring // For the monitoring
HANDLE _eventHandle = nullptr; HANDLE _eventHandle = nullptr;
bool _isMonitoringOn = false; bool _isMonitoringOn = false;