Skip to content

Commit 8136de3

Browse files
authored
CollectionInterface v1.3.0: Automatically generate overrideMap updates for functionList downloads (#20)
Automate OverrideMap Updates: - incorporate tinyxml2 library to read/edit/write the XML file - prompt user when UDL+FL or FL are being downloaded - automatically bring in the right XML attributes for the `<association>` tag(s) for each UDL that has a FL - save changed overrideMap (make use of existing FL prompt-for-UAC, if necessary) closes #2
1 parent 597f1e5 commit 8136de3

19 files changed

+5832
-61
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ The **Download** button will download and install the UDL definition file (and t
3030

3131
The progress bar near the bottom will be at 100% when the Download is complete. Once you pick a different UDL, the progress bar resets to 0%.
3232

33+
v1.3.0: If you include any FunctionList files, the plugin will ask if you want to update your `overrideMap.xml`, which will enable those functionLists automatically, rather than requiring you to manually edit that file and restart.
34+
3335
### AutoCompletion tab
3436

3537
![](.images/download_ac.png)
@@ -54,6 +56,8 @@ The **Download** button will download and install the FunctionList definition fi
5456

5557
The progress bar near the bottom will be at 100% when the Download is complete. Once you pick a different UDL, the progress bar resets to 0%.
5658

59+
v1.3.0: If you include any FunctionList files, the plugin will ask if you want to update your `overrideMap.xml`, which will enable those functionLists automatically, rather than requiring you to manually edit that file and restart.
60+
5761
### Theme tab
5862

5963
![](.images/download_th.png)

src/Classes/NppMetaClass.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
#include "NppMetaClass.h"
22
#include <pathcch.h>
33
#include <shlwapi.h>
4+
#include "Version.h"
45

5-
// Global NppMetaInfo object
6-
NppMetaInfo gNppMetaInfo;
7-
8-
NppMetaInfo::NppMetaInfo(void)
9-
: hwnd(nppData)
6+
NppMetaInfo::NppMetaInfo(std::wstring wsPluginName)
7+
: hwnd(nppData), _wsPluginName(wsPluginName)
108
{
119
_isInitialized = false;
1210
dir = { L"", L"", L"", L"", L"", L"", L"", L"", L"", L"", L"" };
@@ -49,7 +47,7 @@ void NppMetaInfo::populate(void)
4947
::SendMessage(hwnd._nppHandle, NPPM_GETPLUGINSCONFIGDIR, sz, reinterpret_cast<LPARAM>(pluginCfgDir.data()));
5048
pcjHelper::delNull(pluginCfgDir);
5149
dir.cfgPluginConfig = pluginCfgDir;
52-
dir.cfgPluginConfigMyDir = pluginCfgDir + L"\\ConfigUpdater";
50+
dir.cfgPluginConfigMyDir = pluginCfgDir + L"\\" + _wsPluginName;
5351

5452
// %AppData%\Notepad++\Plugins or equiv
5553
// since it's removing the tail, it will never be longer than pluginCfgDir; since it's in-place, initialize with the first

src/Classes/NppMetaClass.h

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class NppMetaInfo {
88
public:
99
// constructor
10-
NppMetaInfo(void);
10+
NppMetaInfo(std::wstring wsPluginName);
1111

1212
// Run this in the constructor for any class that uses it
1313
// (internally tracks whether it's been populated already, and doesn't duplicate effort)
@@ -17,17 +17,17 @@ class NppMetaInfo {
1717

1818
struct {
1919
std::wstring
20-
app,
21-
appDataEquiv,
22-
appThemes,
23-
appExePath,
24-
cfg,
25-
cfgPluginConfig,
26-
cfgPluginConfigMyDir,
27-
cfgUdl,
28-
cfgFunctionList,
29-
cfgAutoCompletion,
30-
cfgThemes;
20+
app, // directory where the executable is found
21+
appDataEquiv, // %AppData% or .app, ignoring Cloud or -settingsDir
22+
appThemes, // .app\Themes\ subfolder (alternate themes always under .app\ directory)
23+
appExePath, // .app\notepad++.exe == "path\to\notepad++.exe" (includes .exe file)
24+
cfg, // SettingsDir >> CloudDir >> AppData(equiv)
25+
cfgPluginConfig, // .appDataEquiv\plugins\Config
26+
cfgPluginConfigMyDir, // .appDataEquiv\plugins\Config\wsPluginName
27+
cfgUdl, // .cfg\userDefinedLangs\ subdir (SettingsDir >> Cloud Dir >> Portable >> AppData(equiv))
28+
cfgFunctionList, // .appDataEquiv\functionList\ subdir (skip Cloud or -settingsDir)
29+
cfgAutoCompletion, // .app\autoCompletion\ subdir
30+
cfgThemes; // .cfg\Themes\ subfoloder (SettingsDir >> Cloud Dir >> Portable >> AppData(equiv))
3131
} dir;
3232

3333
////////////////////////////////
@@ -53,6 +53,7 @@ class NppMetaInfo {
5353
private:
5454
bool _isInitialized;
5555
std::wstring _askSettingsDir(void);
56+
std::wstring _wsPluginName;
5657
};
5758

5859
// make sure everyone who can see this class can see the global instance
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
#include "OverrideMapUpdaterClass.h"
2+
3+
// Experiment with concepts needed, might eventually morph into actual behavior
4+
OverrideMapUpdater::OverrideMapUpdater(void)
5+
{
6+
gNppMetaInfo.populate();
7+
8+
// 1. Pick correct file: "cfg" directory (Settings/Cloud/AppData) or "app" directory, whichever exists
9+
_wsOverMapPath = gNppMetaInfo.dir.cfgFunctionList + L"\\overrideMap.xml";
10+
_wsAppOverMapPath = gNppMetaInfo.dir.app + L"\\functionList\\overrideMap.xml";
11+
bool needWriteToAppDir = false;
12+
bool needToCreateFile = false;
13+
14+
if (!PathFileExists(_wsOverMapPath.c_str())) {
15+
// since .cfgFunctionList\overrideMap.xml doesn't exist, try to copy from app dir instead
16+
if (PathFileExists(_wsAppOverMapPath.c_str())) {
17+
// check if writeable
18+
if (pcjHelper::is_dir_writable(gNppMetaInfo.dir.cfgFunctionList)) {
19+
// if writable, copy the app version to the cfg location
20+
CopyFile(_wsAppOverMapPath.c_str(), _wsOverMapPath.c_str(), TRUE);
21+
}
22+
else {
23+
// if not writable, then need to write into the app directory instead
24+
needWriteToAppDir = true;
25+
}
26+
}
27+
else {
28+
// if neither XML exists, need to create one of them
29+
needToCreateFile = true;
30+
// pick the right one based on writability of cfg directory
31+
needWriteToAppDir = !pcjHelper::is_dir_writable(gNppMetaInfo.dir.cfgFunctionList);
32+
}
33+
34+
// change the destination path if it needs to be in the app directory
35+
if (needWriteToAppDir) _wsOverMapPath = _wsAppOverMapPath;
36+
}
37+
38+
// 2: need XML object
39+
pOverrideMapXML = new tinyxml2::XMLDocument; // create new one (old is discarded, if it exists)
40+
if (needToCreateFile) {
41+
// create the basic structure
42+
pRoot = pOverrideMapXML->NewElement("NotepadPlus");
43+
pOverrideMapXML->InsertFirstChild(pRoot);
44+
45+
pFunctionList = pRoot->InsertNewChildElement("functionList");
46+
pAssociationMap = pFunctionList->InsertNewChildElement("associationMap");
47+
48+
pAssociationMap->InsertEndChild(
49+
pOverrideMapXML->NewComment(
50+
" \n"
51+
" See https://github.com/notepad-plus-plus/notepad-plus-plus/blob/master/PowerEditor/installer/functionList/overrideMap.xml for the \"official\" default for this file\n"
52+
" tincluding the default id-vs-langID values\n"
53+
" \n"
54+
" Each functionlist parse rule links to a language ID(\"langID\") or a UDL name.\n"
55+
" Examples:\n"
56+
" <association id=\"my_perl.xml\" langID=\"21\" />\n"
57+
" <association id=\"nppexec.xml\" userDefinedLangName=\"NppExec\" />\n"
58+
" "
59+
)
60+
);
61+
pAssociationMap->InsertEndChild(
62+
pOverrideMapXML->NewComment(" ==================== User Defined Languages ============================ ")
63+
);
64+
65+
66+
/*tinyxml2::XMLElement* pAssoc =*/ add_udl_assoc("nppexec.xml", "NppExec");
67+
}
68+
else {
69+
// load file and extract the main elements
70+
tinyxml2::XMLError eResult = pOverrideMapXML->LoadFile(sOverMapPath().c_str());
71+
if(_xml_check_result(eResult, pOverrideMapXML, wsOverMapPath())) return;
72+
73+
pRoot = pOverrideMapXML->FirstChildElement("NotepadPlus");
74+
if (!pRoot) {
75+
_xml_check_result(tinyxml2::XML_ERROR_PARSING, pOverrideMapXML, wsOverMapPath());
76+
return;
77+
}
78+
79+
pFunctionList = pRoot->FirstChildElement("functionList");
80+
if (!pFunctionList) {
81+
_xml_check_result(tinyxml2::XML_ERROR_PARSING, pOverrideMapXML, wsOverMapPath());
82+
return;
83+
}
84+
85+
pAssociationMap = pFunctionList->FirstChildElement("associationMap");
86+
if (!pAssociationMap) {
87+
_xml_check_result(tinyxml2::XML_ERROR_PARSING, pOverrideMapXML, wsOverMapPath());
88+
return;
89+
}
90+
}
91+
}
92+
93+
// add an <association> tag for a given UDL
94+
// converts wstring to string first
95+
tinyxml2::XMLElement* OverrideMapUpdater::add_udl_assoc(std::wstring wsFilename, std::wstring wsUDLname)
96+
{
97+
return add_udl_assoc(
98+
pcjHelper::wstring_to_utf8(wsFilename),
99+
pcjHelper::wstring_to_utf8(wsUDLname)
100+
);
101+
}
102+
103+
// add an <association> tag for a given UDL
104+
// (skip if already exists)
105+
tinyxml2::XMLElement* OverrideMapUpdater::add_udl_assoc(std::string sFilename, std::string sUDLname)
106+
{
107+
// first check if the UDL already exists in the <association> structure
108+
tinyxml2::XMLElement* pExist = _find_element_with_attribute_value(pAssociationMap, nullptr, "association", "userDefinedLangName", sUDLname, true);
109+
if (pExist) return pExist;
110+
111+
// if it doesn't, then add it
112+
tinyxml2::XMLElement* pAssoc = pAssociationMap->InsertNewChildElement("association");
113+
if (pAssoc) {
114+
pAssoc->SetAttribute("id", sFilename.c_str());
115+
pAssoc->SetAttribute("userDefinedLangName", sUDLname.c_str());
116+
}
117+
return pAssoc;
118+
}
119+
120+
// add <association> tags for each filename,udl pair in the map
121+
std::vector<tinyxml2::XMLElement*> OverrideMapUpdater::add_udl_assoc(std::map<std::string, std::string> msUdls)
122+
{
123+
std::vector<tinyxml2::XMLElement*> vpAssoc;
124+
for (const auto& pair : msUdls) {
125+
vpAssoc.push_back(add_udl_assoc(pair.first, pair.second));
126+
}
127+
return vpAssoc;
128+
}
129+
130+
// add <association> tags for each filename,udl pair in the map
131+
std::vector<tinyxml2::XMLElement*> OverrideMapUpdater::add_udl_assoc(std::map<std::wstring, std::wstring> mwsUdls)
132+
{
133+
std::vector<tinyxml2::XMLElement*> vpAssoc;
134+
for (const auto& pair : mwsUdls) {
135+
vpAssoc.push_back(add_udl_assoc(pair.first, pair.second));
136+
}
137+
return vpAssoc;
138+
}
139+
140+
// private: case-insensitive std::string equality check
141+
bool _string_insensitive_eq(std::string a, std::string b)
142+
{
143+
std::string a_copy = "";
144+
std::string b_copy = "";
145+
146+
// ignore conversion of int to char implicit in the <algorithm>std::transform, which I have no control over
147+
#pragma warning(push)
148+
#pragma warning(disable: 4244)
149+
for (size_t i = 0; i < a.size(); i++) { a_copy += std::tolower(a[i]); }
150+
for (size_t i = 0; i < b.size(); i++) { b_copy += std::tolower(b[i]); }
151+
#pragma warning(pop)
152+
return a_copy == b_copy;
153+
}
154+
155+
// private: case-insensitive std::string less-than check
156+
bool _string_insensitive_lt(std::string a, std::string b)
157+
{
158+
std::string a_copy = "";
159+
std::string b_copy = "";
160+
161+
// ignore conversion of int to char implicit in the <algorithm>std::transform, which I have no control over
162+
#pragma warning(push)
163+
#pragma warning(disable: 4244)
164+
for (size_t i = 0; i < a.size(); i++) { a_copy += std::tolower(a[i]); }
165+
for (size_t i = 0; i < b.size(); i++) { b_copy += std::tolower(b[i]); }
166+
#pragma warning(pop)
167+
return a_copy < b_copy;
168+
}
169+
170+
171+
// look for an element, based on {Parent, FirstChild, or both} which is of a specific ElementType, having a specific AttributeName with specific AttributeValue
172+
tinyxml2::XMLElement* OverrideMapUpdater::_find_element_with_attribute_value(tinyxml2::XMLElement* pParent, tinyxml2::XMLElement* pFirst, std::string sElementType, std::string sAttributeName, std::string sAttributeValue, bool caseSensitive)
173+
{
174+
if (!pParent && !pFirst) return nullptr;
175+
tinyxml2::XMLElement* pMyParent = pParent ? pParent->ToElement() : pFirst->Parent()->ToElement();
176+
tinyxml2::XMLElement* pFCE = pMyParent->FirstChildElement(sElementType.c_str());
177+
if (!pFirst && !pFCE) return nullptr;
178+
tinyxml2::XMLElement* pFoundElement = pFirst ? pFirst->ToElement() : pFCE->ToElement();
179+
while (pFoundElement) {
180+
// if this node has the right attribute pair, great!
181+
if (caseSensitive) {
182+
if (pFoundElement->Attribute(sAttributeName.c_str(), sAttributeValue.c_str()))
183+
return pFoundElement;
184+
}
185+
else {
186+
const char* cAttrValue = pFoundElement->Attribute(sAttributeName.c_str());
187+
if (cAttrValue) {
188+
if (_string_insensitive_eq(sAttributeValue, cAttrValue))
189+
return pFoundElement;
190+
}
191+
}
192+
193+
// otherwise, move on to next
194+
pFoundElement = pFoundElement->NextSiblingElement(sElementType.c_str());
195+
}
196+
return pFoundElement;
197+
}
198+
199+
200+
// compares the XMLError result to XML_SUCCESS, and returns a TRUE boolean to indicate failure
201+
// p_doc defaults to nullptr, wsFilePath to L""
202+
bool OverrideMapUpdater::_xml_check_result(tinyxml2::XMLError a_eResult, tinyxml2::XMLDocument* p_doc, std::wstring wsFilePath)
203+
{
204+
if (a_eResult != tinyxml2::XML_SUCCESS) {
205+
std::string sMsg = std::string("XML Error #") + std::to_string(static_cast<int>(a_eResult));
206+
if (p_doc != NULL) {
207+
sMsg += std::string(": ") + std::string(p_doc->ErrorStr());
208+
if (p_doc->ErrorLineNum()) {
209+
sMsg += "\n\nI will try to open the file near that error.";
210+
}
211+
}
212+
::MessageBoxA(NULL, sMsg.c_str(), "XML Error", MB_ICONWARNING | MB_OK);
213+
if (p_doc != NULL && p_doc->ErrorLineNum() && wsFilePath.size()) {
214+
if (::SendMessage(gNppMetaInfo.hwnd._nppHandle, NPPM_DOOPEN, 0, reinterpret_cast<LPARAM>(wsFilePath.c_str()))) {
215+
extern NppData nppData; // not in PluginDefinition.h
216+
217+
// Get the current scintilla
218+
int which = -1;
219+
::SendMessage(nppData._nppHandle, NPPM_GETCURRENTSCINTILLA, 0, (LPARAM)&which);
220+
HWND curScintilla = (which < 1) ? nppData._scintillaMainHandle : nppData._scintillaSecondHandle;
221+
222+
// SCI_GOTOLINE in the current scintilla instance
223+
WPARAM zeroLine = static_cast<WPARAM>(p_doc->ErrorLineNum() - 1);
224+
::SendMessage(curScintilla, SCI_GOTOLINE, zeroLine, 0);
225+
226+
// do annotation
227+
::SendMessage(curScintilla, SCI_ANNOTATIONCLEARALL, 0, 0);
228+
::SendMessage(curScintilla, SCI_ANNOTATIONSETVISIBLE, ANNOTATION_BOXED, 0);
229+
::SendMessageA(curScintilla, SCI_ANNOTATIONSETTEXT, zeroLine, reinterpret_cast<LPARAM>(p_doc->ErrorStr()));
230+
231+
// need to stop
232+
// In ConfigUpdater, I needed to set `_doAbort = true;` to exit out of a loop, but that's needed in CollectionInterface
233+
};
234+
}
235+
return true;
236+
}
237+
return false;
238+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#pragma once
2+
#include <string>
3+
#include <vector>
4+
#include <map>
5+
#include <stdexcept>
6+
#include <windows.h>
7+
#include <wininet.h>
8+
#include <pathcch.h>
9+
#include <shlwapi.h>
10+
#include "PluginDefinition.h"
11+
#include "NppMetaClass.h"
12+
#include "tinyxml2.h"
13+
#include "pcjHelper.h"
14+
15+
class OverrideMapUpdater {
16+
public:
17+
// Instantiate OverrideMapUpdater object
18+
OverrideMapUpdater(void);
19+
20+
// add an <association> tag for a given UDL
21+
tinyxml2::XMLElement* OverrideMapUpdater::add_udl_assoc(std::wstring wsFilename, std::wstring wsUDLname);
22+
tinyxml2::XMLElement* OverrideMapUpdater::add_udl_assoc(std::string sFilename, std::string sUDLname);
23+
24+
// add <association> tags for each filename,udl pair in the map
25+
std::vector<tinyxml2::XMLElement*> OverrideMapUpdater::add_udl_assoc(std::map<std::wstring, std::wstring> mwsUdls);
26+
std::vector<tinyxml2::XMLElement*> OverrideMapUpdater::add_udl_assoc(std::map<std::string, std::string> msUdls);
27+
28+
// save the underlying overrideMap.xml
29+
tinyxml2::XMLError OverrideMapUpdater::saveFile(void) { return pOverrideMapXML->SaveFile(sOverMapPath().c_str()); }
30+
tinyxml2::XMLError OverrideMapUpdater::saveFile(std::string sNewPath) { return pOverrideMapXML->SaveFile(sNewPath.c_str()); }
31+
tinyxml2::XMLError OverrideMapUpdater::saveFile(std::wstring wsNewPath) { return pOverrideMapXML->SaveFile(pcjHelper::wstring_to_utf8(wsNewPath).c_str()); }
32+
33+
// getters
34+
std::wstring wsOverMapPath(void) { return _wsOverMapPath; }
35+
std::string sOverMapPath(void) { return pcjHelper::wstring_to_utf8(_wsOverMapPath); }
36+
37+
private:
38+
////////////////
39+
// methods
40+
////////////////
41+
42+
// look for an element, based on {Parent, FirstChild, or both} which is of a specific ElementType, having a specific AttributeName with specific AttributeValue
43+
tinyxml2::XMLElement* _find_element_with_attribute_value(tinyxml2::XMLElement* pParent, tinyxml2::XMLElement* pFirst, std::string sElementType, std::string sAttributeName, std::string sAttributeValue, bool caseSensitive);
44+
45+
// compares the XMLError result to XML_SUCCESS, and returns a TRUE boolean to indicate failure
46+
// p_doc defaults to nullptr, wsFilePath to L""
47+
bool OverrideMapUpdater::_xml_check_result(tinyxml2::XMLError a_eResult, tinyxml2::XMLDocument* p_doc = nullptr, std::wstring wsFilePath = std::wstring(L""));
48+
49+
////////////////
50+
// properties
51+
////////////////
52+
53+
// paths
54+
std::wstring _wsOverMapPath; // <cfg>\functionList\overrideMap.xml path, or <exe> version
55+
std::wstring _wsAppOverMapPath; // <exe>\functionList\overrideMap.xml path
56+
57+
// tinyxml2
58+
tinyxml2::XMLDocument* pOverrideMapXML;
59+
tinyxml2::XMLElement* pRoot, * pFunctionList, * pAssociationMap;
60+
};

0 commit comments

Comments
 (0)