From dfb9b5e330c2fe3dcfea436f969e213f001ece05 Mon Sep 17 00:00:00 2001 From: Don HO Date: Thu, 7 Feb 2019 23:40:17 +0100 Subject: [PATCH] Enhance User Defined Language System for supporting more than one UDL file. Several UDL xml files can be loaded to allow to manage/share UDL more easily. 1. The old file userDefineLang.xml is kept in its old location and is still used. 2. The new folder userDefineLangs is added beside of the old UDL default file. Any UDL xml file can go into the folder userDefineLangs and will be loaded as UDL. 3. A UDL xml file must contain one (or several) user defined language(s). 4. The created UDL via UDL dialog and imported UDL are saved in userDefineLang.xml (default UDL file). https://notepad-plus-plus.org/community/topic/17072/new-enhancement-for-user-defined-language-system --- PowerEditor/src/MISC/Common/Common.cpp | 26 ++ PowerEditor/src/MISC/Common/Common.h | 4 +- PowerEditor/src/Notepad_plus.cpp | 3 +- PowerEditor/src/Parameters.cpp | 259 ++++++++++++++---- PowerEditor/src/Parameters.h | 35 +-- .../ScitillaComponent/UserDefineDialog.cpp | 14 +- .../src/ScitillaComponent/UserDefineDialog.h | 2 - 7 files changed, 264 insertions(+), 79 deletions(-) diff --git a/PowerEditor/src/MISC/Common/Common.cpp b/PowerEditor/src/MISC/Common/Common.cpp index adf5458f..c68758f6 100644 --- a/PowerEditor/src/MISC/Common/Common.cpp +++ b/PowerEditor/src/MISC/Common/Common.cpp @@ -1238,3 +1238,29 @@ bool deleteFileOrFolder(const generic_string& f2delete) delete[] actionFolder; return (res == 0); } + +// Get a vector of full file paths in a given folder. File extension type filter should be *.*, *.xml, *.dll... according the type of file you want to get. +void getFilesInFolder(std::vector& files, const generic_string& extTypeFilter, const generic_string& inFolder) +{ + generic_string filter = inFolder; + PathAppend(filter, extTypeFilter); + + WIN32_FIND_DATA foundData; + HANDLE hFindFile = ::FindFirstFile(filter.c_str(), &foundData); + + if (hFindFile != INVALID_HANDLE_VALUE && !(foundData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + generic_string foundFullPath = inFolder; + PathAppend(foundFullPath, foundData.cFileName); + files.push_back(foundFullPath); + + while (::FindNextFile(hFindFile, &foundData)) + { + generic_string foundFullPath2 = inFolder; + PathAppend(foundFullPath2, foundData.cFileName); + files.push_back(foundFullPath2); + } + } + ::FindClose(hFindFile); +} + diff --git a/PowerEditor/src/MISC/Common/Common.h b/PowerEditor/src/MISC/Common/Common.h index be1e6235..4daebe18 100644 --- a/PowerEditor/src/MISC/Common/Common.h +++ b/PowerEditor/src/MISC/Common/Common.h @@ -195,4 +195,6 @@ bool isAssoCommandExisting(LPCTSTR FullPathName); std::wstring s2ws(const std::string& str); std::string ws2s(const std::wstring& wstr); -bool deleteFileOrFolder(const generic_string& f2delete); \ No newline at end of file +bool deleteFileOrFolder(const generic_string& f2delete); + +void getFilesInFolder(std::vector& files, const generic_string& extTypeFilter, const generic_string& inFolder); diff --git a/PowerEditor/src/Notepad_plus.cpp b/PowerEditor/src/Notepad_plus.cpp index 3d134198..76778a35 100644 --- a/PowerEditor/src/Notepad_plus.cpp +++ b/PowerEditor/src/Notepad_plus.cpp @@ -928,8 +928,7 @@ void Notepad_plus::saveDockingParams() void Notepad_plus::saveUserDefineLangs() { - if (ScintillaEditView::getUserDefineDlg()->isDirty()) - (NppParameters::getInstance())->writeUserDefinedLang(); + (NppParameters::getInstance())->writeNeed2SaveUDL(); } diff --git a/PowerEditor/src/Parameters.cpp b/PowerEditor/src/Parameters.cpp index 1034815f..42a54c0f 100644 --- a/PowerEditor/src/Parameters.cpp +++ b/PowerEditor/src/Parameters.cpp @@ -829,11 +829,6 @@ int FileDialog::_dialogFileBoxId = (NppParameters::getInstance())->getWinVersion NppParameters::NppParameters() { - // init import UDL array - _nbImportedULD = 0; - for (int i = 0 ; i < NB_MAX_IMPORTED_UDL ; ++i) - _importedULD[i] = nullptr; - //Get windows version _winVersion = getWindowsVersion(); @@ -1229,8 +1224,12 @@ bool NppParameters::load() //-----------------------------------// // userDefineLang.xml : for per user // //-----------------------------------// - _userDefineLangPath = _userPath; + generic_string userDefineLangsFolderPath = _userDefineLangPath = _userPath; PathAppend(_userDefineLangPath, TEXT("userDefineLang.xml")); + PathAppend(userDefineLangsFolderPath, TEXT("userDefineLangs")); + + std::vector udlFiles; + getFilesInFolder(udlFiles, TEXT("*.xml"), userDefineLangsFolderPath); _pXmlUserLangDoc = new TiXmlDocument(_userDefineLangPath); loadOkay = _pXmlUserLangDoc->LoadFile(); @@ -1241,7 +1240,27 @@ bool NppParameters::load() isAllLaoded = false; } else - getUserDefineLangsFromXmlTree(); + { + auto r = addUserDefineLangsFromXmlTree(_pXmlUserLangDoc); + if (r.second - r.first > 0) + _pXmlUserLangsDoc.push_back(UdlXmlFileState(_pXmlUserLangDoc, false, r)); + } + + for (const auto& i : udlFiles) + { + auto udlDoc = new TiXmlDocument(i); + loadOkay = udlDoc->LoadFile(); + if (!loadOkay) + { + delete udlDoc; + } + else + { + auto r = addUserDefineLangsFromXmlTree(udlDoc); + if (r.second - r.first > 0) + _pXmlUserLangsDoc.push_back(UdlXmlFileState(udlDoc, false, r)); + } + } //----------------------------------------------// // nativeLang.xml : for per user // @@ -1402,14 +1421,12 @@ void NppParameters::destroyInstance() delete _pXmlDoc; delete _pXmlUserDoc; delete _pXmlUserStylerDoc; - delete _pXmlUserLangDoc; - - for (int i = 0 ; i < _nbImportedULD ; ++i) + + //delete _pXmlUserLangDoc; will be deleted in the vector + for (auto l : _pXmlUserLangsDoc) { - delete _importedULD[i]; - _importedULD[i] = nullptr; + delete l._udlXmlDoc; } - _nbImportedULD = 0; delete _pXmlNativeLangDocA; delete _pXmlToolIconsDoc; @@ -1661,14 +1678,14 @@ bool NppParameters::getUserParametersFromXmlTree() } -bool NppParameters::getUserDefineLangsFromXmlTree(TiXmlDocument *tixmldoc) +std::pair NppParameters::addUserDefineLangsFromXmlTree(TiXmlDocument *tixmldoc) { if (!tixmldoc) - return false; + return std::pair(0, 0); TiXmlNode *root = tixmldoc->FirstChild(TEXT("NotepadPlus")); if (!root) - return false; + return std::pair(0, 0); return feedUserLang(root); } @@ -2588,10 +2605,9 @@ bool NppParameters::getShortcuts(TiXmlNode *node, Shortcut & sc) } -bool NppParameters::feedUserLang(TiXmlNode *node) +std::pair NppParameters::feedUserLang(TiXmlNode *node) { - bool isEverythingOK = true; - bool hasFoundElement = false; + int iBegin = _nbUserLang; for (TiXmlNode *childNode = node->FirstChildElement(TEXT("UserLang")); childNode && (_nbUserLang < NB_MAX_USER_LANG); @@ -2600,7 +2616,6 @@ bool NppParameters::feedUserLang(TiXmlNode *node) const TCHAR *name = (childNode->ToElement())->Attribute(TEXT("name")); const TCHAR *ext = (childNode->ToElement())->Attribute(TEXT("ext")); const TCHAR *udlVersion = (childNode->ToElement())->Attribute(TEXT("udlVersion")); - hasFoundElement = true; try { if (!name || !name[0] || !ext) throw std::runtime_error("NppParameters::feedUserLang : UserLang name is missing"); @@ -2639,26 +2654,29 @@ bool NppParameters::feedUserLang(TiXmlNode *node) } catch (std::exception e) { delete _userLangArray[--_nbUserLang]; - isEverythingOK = false; } } - if (isEverythingOK) - isEverythingOK = hasFoundElement; - return isEverythingOK; + int iEnd = _nbUserLang; + return pair(iBegin, iEnd); } bool NppParameters::importUDLFromFile(generic_string sourceFile) { - if (_nbImportedULD >= NB_MAX_IMPORTED_UDL) - return false; - TiXmlDocument *pXmlUserLangDoc = new TiXmlDocument(sourceFile); bool loadOkay = pXmlUserLangDoc->LoadFile(); if (loadOkay) { - loadOkay = getUserDefineLangsFromXmlTree(pXmlUserLangDoc); + auto r = addUserDefineLangsFromXmlTree(pXmlUserLangDoc); + loadOkay = (r.second - r.first) != 0; + if (loadOkay) + { + _pXmlUserLangsDoc.push_back(UdlXmlFileState(nullptr, true, r)); + + // imported UDL from xml file will be added into default udl, so we should make default udl dirty + setUdlXmlDirtyFromXmlDoc(_pXmlUserLangDoc); + } } - _importedULD[_nbImportedULD++] = pXmlUserLangDoc; + delete pXmlUserLangDoc; return loadOkay; } @@ -2863,33 +2881,97 @@ bool NppParameters::writeSettingsFilesOnCloudForThe1stTime(const generic_string return true; } +/* +Default UDL + Created + Imported -void NppParameters::writeUserDefinedLang() +*/ +void NppParameters::writeDefaultUDL() { - if (!_pXmlUserLangDoc) + bool firstCleanDone = false; + for (auto udl : _pXmlUserLangsDoc) { - //do the treatment - _pXmlUserLangDoc = new TiXmlDocument(_userDefineLangPath); + if (!_pXmlUserLangDoc) + { + _pXmlUserLangDoc = new TiXmlDocument(_userDefineLangPath); + } + + bool toDelete = (udl._indexRange.second - udl._indexRange.first) == 0; + if ((!udl._udlXmlDoc || udl._udlXmlDoc == _pXmlUserLangDoc) && udl._isDirty && !toDelete) // new created or/and imported UDL plus _pXmlUserLangDoc (if exist) + { + TiXmlNode *root = _pXmlUserLangDoc->FirstChild(TEXT("NotepadPlus")); + if (root && !firstCleanDone) + { + _pXmlUserLangDoc->RemoveChild(root); + _pXmlUserLangDoc->InsertEndChild(TiXmlElement(TEXT("NotepadPlus"))); + firstCleanDone = true; + } + + root = _pXmlUserLangDoc->FirstChild(TEXT("NotepadPlus")); + + for (int i = udl._indexRange.first; i < udl._indexRange.second; ++i) + { + insertUserLang2Tree(root, _userLangArray[i]); + } + } } - //before remove the branch, we allocate and copy the TCHAR * which will be destroyed + if (firstCleanDone) + { + _pXmlUserLangDoc->SaveFile(); + } + else + { + if (::PathFileExists(_userDefineLangPath.c_str())) + { + ::DeleteFile(_userDefineLangPath.c_str()); + } + } +} + +void NppParameters::writeNonDefaultUDL() +{ + for (auto udl : _pXmlUserLangsDoc) + { + if (udl._isDirty && udl._udlXmlDoc != nullptr && udl._udlXmlDoc != _pXmlUserLangDoc) + { + if (udl._indexRange.second == udl._indexRange.first) // no more udl for this xmldoc container + { + // no need to save, delete file + const TCHAR* docFilePath = udl._udlXmlDoc->Value(); + if (docFilePath && ::PathFileExists(docFilePath)) + { + ::DeleteFile(docFilePath); + } + } + else + { + TiXmlNode *root = udl._udlXmlDoc->FirstChild(TEXT("NotepadPlus")); + if (root) + { + udl._udlXmlDoc->RemoveChild(root); + } + + udl._udlXmlDoc->InsertEndChild(TiXmlElement(TEXT("NotepadPlus"))); + + root = udl._udlXmlDoc->FirstChild(TEXT("NotepadPlus")); + + for (int i = udl._indexRange.first; i < udl._indexRange.second; ++i) + { + insertUserLang2Tree(root, _userLangArray[i]); + } + udl._udlXmlDoc->SaveFile(); + } + } + } +} + +void NppParameters::writeNeed2SaveUDL() +{ stylerStrOp(DUP); - TiXmlNode *root = _pXmlUserLangDoc->FirstChild(TEXT("NotepadPlus")); - if (root) - { - _pXmlUserLangDoc->RemoveChild(root); - } - - _pXmlUserLangDoc->InsertEndChild(TiXmlElement(TEXT("NotepadPlus"))); - - root = _pXmlUserLangDoc->FirstChild(TEXT("NotepadPlus")); - - for (int i = 0 ; i < _nbUserLang ; ++i) - { - insertUserLang2Tree(root, _userLangArray[i]); - } - _pXmlUserLangDoc->SaveFile(); + writeDefaultUDL(); + writeNonDefaultUDL(); + stylerStrOp(FREE); } @@ -3142,11 +3224,18 @@ int NppParameters::addUserLangToEnd(const UserLangContainer & userLang, const TC { if (isExistingUserLangName(newName)) return -1; - + unsigned char iBegin = _nbUserLang; _userLangArray[_nbUserLang] = new UserLangContainer(); *(_userLangArray[_nbUserLang]) = userLang; _userLangArray[_nbUserLang]->_name = newName; ++_nbUserLang; + unsigned char iEnd = _nbUserLang; + + _pXmlUserLangsDoc.push_back(UdlXmlFileState(nullptr, true, make_pair(iBegin, iEnd))); + + // imported UDL from xml file will be added into default udl, so we should make default udl dirty + setUdlXmlDirtyFromXmlDoc(_pXmlUserLangDoc); + return _nbUserLang-1; } @@ -3160,6 +3249,8 @@ void NppParameters::removeUserLang(size_t index) for (int32_t i = static_cast(index); i < (_nbUserLang - 1); ++i) _userLangArray[i] = _userLangArray[i+1]; _nbUserLang--; + + removeIndexFromXmlUdls(index); } @@ -6625,6 +6716,74 @@ void NppParameters::safeWow64EnableWow64FsRedirection(BOOL Wow64FsEnableRedirect } } +void NppParameters::setUdlXmlDirtyFromIndex(size_t i) +{ + for (auto& uxfs : _pXmlUserLangsDoc) + { + if (i >= uxfs._indexRange.first && i < uxfs._indexRange.second) + { + uxfs._isDirty = true; + return; + } + } +} + +/* +Considering we have done: +load default UDL: 3 languges +load a UDL file: 1 languge +load a UDL file: 2 languges +create a UDL: 1 languge +imported a UDL: 1 languge + +the evolution to remove UDL one by one: + +0[D1] 0[D1] 0[D1] [D1] [D1] +1[D2] 1[D2] 1[D2] 0[D2] [D2] +2[D3] [DUDL, <0,3>] 2[D3] [DUDL, <0,3>] 2[D3] [DUDL, <0,3>] 1[D3] [DUDL, <0,2>] [D3] [DUDL, <0,0>] +3[U1] [NUDL, <3,4>] 3[U1] [NUDL, <3,4>] 3[U1] [NUDL, <3,4>] 2[U1] [NUDL, <2,3>] [U1] [NUDL, <0,0>] +4[U2] 4[U2] [U2] [U2] [U2] +5[U2] [NUDL, <4,6>] 5[U2] [NUDL, <4,6>] 4[U2] [NUDL, <4,5>] 3[U2] [NUDL, <3,4>] 0[U2] [NUDL, <0,1>] +6[C1] [NULL, <6,7>] [C1] [NULL, <6,6>] [C1] [NULL, <5,5>] [C1] [NULL, <4,4>] [C1] [NULL, <1,1>] +7[I1] [NULL, <7,8>] 6[I1] [NULL, <6,7>] 5[I1] [NULL, <5,6>] 4[I1] [NULL, <4,5>] 1[I1] [NULL, <1,2>] +*/ +void NppParameters::removeIndexFromXmlUdls(size_t i) +{ + bool isUpdateBegin = false; + for (auto& uxfs : _pXmlUserLangsDoc) + { + // Find index + if (!isUpdateBegin && (i >= uxfs._indexRange.first && i < uxfs._indexRange.second)) // found it + { + if (uxfs._indexRange.second > 0) + uxfs._indexRange.second -= 1; + uxfs._isDirty = true; + + isUpdateBegin = true; + } + + // Update + else if (isUpdateBegin) + { + if (uxfs._indexRange.first > 0) + uxfs._indexRange.first -= 1; + if (uxfs._indexRange.second > 0) + uxfs._indexRange.second -= 1; + } + } +} + +void NppParameters::setUdlXmlDirtyFromXmlDoc(const TiXmlDocument* xmlDoc) +{ + for (auto& uxfs : _pXmlUserLangsDoc) + { + if (xmlDoc == uxfs._udlXmlDoc) + { + uxfs._isDirty = true; + return; + } + } +} Date::Date(const TCHAR *dateStr) { diff --git a/PowerEditor/src/Parameters.h b/PowerEditor/src/Parameters.h index 6e1ee46a..ed8c27e4 100644 --- a/PowerEditor/src/Parameters.h +++ b/PowerEditor/src/Parameters.h @@ -1267,6 +1267,14 @@ private: }; +struct UdlXmlFileState final { + TiXmlDocument* _udlXmlDoc = nullptr; + bool _isDirty = false; + std::pair _indexRange; + + UdlXmlFileState(TiXmlDocument* doc, bool isDirty, std::pair range) : _udlXmlDoc(doc), _isDirty(isDirty), _indexRange(range) {}; +}; + const int NB_LANG = 100; const bool DUP = true; const bool FREE = false; @@ -1274,9 +1282,6 @@ const bool FREE = false; const int RECENTFILES_SHOWFULLPATH = -1; const int RECENTFILES_SHOWONLYFILENAME = 0; - - - class NppParameters final { public: @@ -1414,7 +1419,9 @@ public: void getExternalLexerFromXmlTree(TiXmlDocument *doc); std::vector * getExternalLexerDoc() { return &_pXmlExternalLexerDoc; }; - void writeUserDefinedLang(); + void writeDefaultUDL(); + void writeNonDefaultUDL(); + void writeNeed2SaveUDL(); void writeShortcuts(); void writeSession(const Session & session, const TCHAR *fileName = NULL); bool writeFindHistory(); @@ -1625,6 +1632,9 @@ public: generic_string static getSpecialFolderLocation(int folderKind); + void setUdlXmlDirtyFromIndex(size_t i); + void setUdlXmlDirtyFromXmlDoc(const TiXmlDocument* xmlDoc); + void removeIndexFromXmlUdls(size_t i); private: NppParameters(); @@ -1636,19 +1646,14 @@ private: TiXmlDocument *_pXmlUserDoc = nullptr; TiXmlDocument *_pXmlUserStylerDoc = nullptr; TiXmlDocument *_pXmlUserLangDoc = nullptr; + std::vector _pXmlUserLangsDoc; TiXmlDocument *_pXmlToolIconsDoc = nullptr; TiXmlDocument *_pXmlShortcutDoc = nullptr; TiXmlDocument *_pXmlSessionDoc = nullptr; TiXmlDocument *_pXmlBlacklistDoc = nullptr; - - TiXmlDocument *_importedULD[NB_MAX_IMPORTED_UDL]; TiXmlDocumentA *_pXmlNativeLangDocA = nullptr; TiXmlDocumentA *_pXmlContextMenuDocA = nullptr; - - int _nbImportedULD; - - std::vector _pXmlExternalLexerDoc; @@ -1669,7 +1674,7 @@ private: FindHistory _findHistory; UserLangContainer *_userLangArray[NB_MAX_USER_LANG]; - int _nbUserLang = 0; + unsigned char _nbUserLang = 0; // won't be exceeded to 255; generic_string _userDefineLangPath; ExternalLangContainer *_externalLangArray[NB_MAX_EXTERNAL_LANG]; int _nbExternalLang = 0; @@ -1769,11 +1774,7 @@ private: void getLangKeywordsFromXmlTree(); bool getUserParametersFromXmlTree(); bool getUserStylersFromXmlTree(); - bool getUserDefineLangsFromXmlTree(TiXmlDocument *tixmldoc); - bool getUserDefineLangsFromXmlTree() - { - return getUserDefineLangsFromXmlTree(_pXmlUserLangDoc); - } + std::pair addUserDefineLangsFromXmlTree(TiXmlDocument *tixmldoc); bool getShortcutsFromXmlTree(); @@ -1793,7 +1794,7 @@ private: void feedProjectPanelsParameters(TiXmlNode *node); void feedFileBrowserParameters(TiXmlNode *node); bool feedStylerArray(TiXmlNode *node); - bool feedUserLang(TiXmlNode *node); + std::pair feedUserLang(TiXmlNode *node); void feedUserStyles(TiXmlNode *node); void feedUserKeywordList(TiXmlNode *node); void feedUserSettings(TiXmlNode *node); diff --git a/PowerEditor/src/ScitillaComponent/UserDefineDialog.cpp b/PowerEditor/src/ScitillaComponent/UserDefineDialog.cpp index 8b85662b..7c977c1b 100644 --- a/PowerEditor/src/ScitillaComponent/UserDefineDialog.cpp +++ b/PowerEditor/src/ScitillaComponent/UserDefineDialog.cpp @@ -930,12 +930,13 @@ void UserDefineDialog::enableLangAndControlsBy(size_t index) void UserDefineDialog::updateDlg() { - if (!_isDirty) - { - int i = static_cast(::SendDlgItemMessage(_hSelf, IDC_LANGNAME_COMBO, CB_GETCURSEL, 0, 0)); - if (i > 0) - _isDirty = true; - } + int i = static_cast(::SendDlgItemMessage(_hSelf, IDC_LANGNAME_COMBO, CB_GETCURSEL, 0, 0)); + if (i > 0) // the first menu item is generic UDL + { + NppParameters *pNppParam = NppParameters::getInstance(); + pNppParam->setUdlXmlDirtyFromIndex(i - 1); + } + ::SendDlgItemMessage(_hSelf, IDC_LANGNAME_IGNORECASE_CHECK, BM_SETCHECK, _pUserLang->_isCaseIgnored, 0); _folderStyleDlg.updateDlg(); _keyWordsStyleDlg.updateDlg(); @@ -1265,7 +1266,6 @@ INT_PTR CALLBACK UserDefineDialog::run_dlgProc(UINT message, WPARAM wParam, LPAR auto i = ::SendDlgItemMessage(_hSelf, IDC_LANGNAME_COMBO, CB_GETCURSEL, 0, 0); reloadLangCombo(); ::SendDlgItemMessage(_hSelf, IDC_LANGNAME_COMBO, CB_SETCURSEL, i, 0); - _isDirty = true; printStr(TEXT("Import successful.")); } else diff --git a/PowerEditor/src/ScitillaComponent/UserDefineDialog.h b/PowerEditor/src/ScitillaComponent/UserDefineDialog.h index cf341170..2a83c4e6 100644 --- a/PowerEditor/src/ScitillaComponent/UserDefineDialog.h +++ b/PowerEditor/src/ScitillaComponent/UserDefineDialog.h @@ -353,7 +353,6 @@ public : void changeStyle(); bool isDocked() const {return _status == DOCK;}; void setDockStatus(bool isDocked) {_status = isDocked;}; - bool isDirty() const {return _isDirty;}; HWND getFolderHandle() const { return _folderStyleDlg.getHSelf(); }; @@ -384,7 +383,6 @@ private : int _currentHight = 0; int _yScrollPos = 0; int _prevHightVal = 0; - bool _isDirty = false; void getActualPosSize() { ::GetWindowRect(_hSelf, &_dlgPos); _dlgPos.right -= _dlgPos.left;