diff --git a/ALL.vs2017.sln b/ALL.vs2017.sln
index 3edda4c2b66..857536ada85 100644
--- a/ALL.vs2017.sln
+++ b/ALL.vs2017.sln
@@ -443,6 +443,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Plugins\src_VCPP\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "editlibparsers", "Externals\crystaledit\editlib\editlibparsers.vcxitems", "{4170552A-09E2-4FAC-B71D-0E2F5EB3C869}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FilterEngine", "Src\FilterEngine\FilterEngine.vcxitems", "{9C37E5D8-1DC0-4EAC-AADB-5FC8BE4FB1BC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
@@ -1388,20 +1390,24 @@ Global
Externals\xdiff\xdiff.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
Src\CompareEngines\CompareEngines.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
Src\diffutils\diffutils.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
+ Src\FilterEngine\FilterEngine.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{7354ba4f-8dab-46ee-a5a2-a148d6ef2443}*SharedItemsImports = 9
Externals\crystaledit\editlib\editlib.vcxitems*{7515ac3c-389a-44cd-b940-a59dde5b8ae3}*SharedItemsImports = 9
+ Src\FilterEngine\FilterEngine.vcxitems*{9c37e5d8-1dc0-4eac-aadb-5fc8be4fb1bc}*SharedItemsImports = 9
Externals\googletest\googletest\googletest.vcxitems*{9ee35458-b145-444f-92b7-27ff72112c42}*SharedItemsImports = 9
Externals\crystaledit\editlib\editlib.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlibparsers.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Externals\xdiff\xdiff.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Src\CompareEngines\CompareEngines.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Src\diffutils\diffutils.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
+ Src\FilterEngine\FilterEngine.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{a644fba4-d76e-4500-b4b7-04d7a245359a}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{aa88b46e-b2e2-4b03-8cd5-1e9d60db6ab2}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlibparsers.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Externals\xdiff\xdiff.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Src\CompareEngines\CompareEngines.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Src\diffutils\diffutils.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
+ Src\FilterEngine\FilterEngine.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{bd0c5fe1-8457-49c2-8801-0c99a6e6cc03}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlib.vcxitems*{c347d6ae-7a2b-4ed0-97ad-2595e1c5d7dd}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlibparsers.vcxitems*{c347d6ae-7a2b-4ed0-97ad-2595e1c5d7dd}*SharedItemsImports = 4
diff --git a/ALL.vs2019.sln b/ALL.vs2019.sln
index c87c50a4b8d..8b1a0bd2f3f 100644
--- a/ALL.vs2019.sln
+++ b/ALL.vs2019.sln
@@ -454,6 +454,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Plugins\src_VCPP\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "editlibparsers", "Externals\crystaledit\editlib\editlibparsers.vcxitems", "{4170552A-09E2-4FAC-B71D-0E2F5EB3C869}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FilterEngine", "Src\FilterEngine\FilterEngine.vcxitems", "{9C37E5D8-1DC0-4EAC-AADB-5FC8BE4FB1BC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
@@ -1457,20 +1459,24 @@ Global
Externals\xdiff\xdiff.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
Src\CompareEngines\CompareEngines.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
Src\diffutils\diffutils.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
+ Src\FilterEngine\FilterEngine.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{7354ba4f-8dab-46ee-a5a2-a148d6ef2443}*SharedItemsImports = 9
Externals\crystaledit\editlib\editlib.vcxitems*{7515ac3c-389a-44cd-b940-a59dde5b8ae3}*SharedItemsImports = 9
+ Src\FilterEngine\FilterEngine.vcxitems*{9c37e5d8-1dc0-4eac-aadb-5fc8be4fb1bc}*SharedItemsImports = 9
Externals\googletest\googletest\googletest.vcxitems*{9ee35458-b145-444f-92b7-27ff72112c42}*SharedItemsImports = 9
Externals\crystaledit\editlib\editlib.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlibparsers.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Externals\xdiff\xdiff.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Src\CompareEngines\CompareEngines.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Src\diffutils\diffutils.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
+ Src\FilterEngine\FilterEngine.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{a644fba4-d76e-4500-b4b7-04d7a245359a}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{aa88b46e-b2e2-4b03-8cd5-1e9d60db6ab2}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlibparsers.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Externals\xdiff\xdiff.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Src\CompareEngines\CompareEngines.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Src\diffutils\diffutils.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
+ Src\FilterEngine\FilterEngine.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{bd0c5fe1-8457-49c2-8801-0c99a6e6cc03}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlib.vcxitems*{c347d6ae-7a2b-4ed0-97ad-2595e1c5d7dd}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlibparsers.vcxitems*{c347d6ae-7a2b-4ed0-97ad-2595e1c5d7dd}*SharedItemsImports = 4
diff --git a/ALL.vs2022.sln b/ALL.vs2022.sln
index 71902f72ba0..7ee34f554ef 100644
--- a/ALL.vs2022.sln
+++ b/ALL.vs2022.sln
@@ -454,6 +454,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Plugins\src_VCPP\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "editlibparsers", "Externals\crystaledit\editlib\editlibparsers.vcxitems", "{4170552A-09E2-4FAC-B71D-0E2F5EB3C869}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FilterEngine", "Src\FilterEngine\FilterEngine.vcxitems", "{9C37E5D8-1DC0-4EAC-AADB-5FC8BE4FB1BC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
@@ -1457,20 +1459,24 @@ Global
Externals\xdiff\xdiff.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
Src\CompareEngines\CompareEngines.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
Src\diffutils\diffutils.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
+ Src\FilterEngine\FilterEngine.vcxitems*{733e7c0b-ac3d-47ac-a8da-e13644d6294d}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{7354ba4f-8dab-46ee-a5a2-a148d6ef2443}*SharedItemsImports = 9
Externals\crystaledit\editlib\editlib.vcxitems*{7515ac3c-389a-44cd-b940-a59dde5b8ae3}*SharedItemsImports = 9
+ Src\FilterEngine\FilterEngine.vcxitems*{9c37e5d8-1dc0-4eac-aadb-5fc8be4fb1bc}*SharedItemsImports = 9
Externals\googletest\googletest\googletest.vcxitems*{9ee35458-b145-444f-92b7-27ff72112c42}*SharedItemsImports = 9
Externals\crystaledit\editlib\editlib.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlibparsers.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Externals\xdiff\xdiff.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Src\CompareEngines\CompareEngines.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Src\diffutils\diffutils.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
+ Src\FilterEngine\FilterEngine.vcxitems*{9fda4af0-ccfd-4812-bdb9-53efedb32bde}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{a644fba4-d76e-4500-b4b7-04d7a245359a}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{aa88b46e-b2e2-4b03-8cd5-1e9d60db6ab2}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlibparsers.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Externals\xdiff\xdiff.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Src\CompareEngines\CompareEngines.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Src\diffutils\diffutils.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
+ Src\FilterEngine\FilterEngine.vcxitems*{ab827c6b-5116-408f-b453-e2075e9b73b4}*SharedItemsImports = 4
Plugins\src_VCPP\Common\Common.vcxitems*{bd0c5fe1-8457-49c2-8801-0c99a6e6cc03}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlib.vcxitems*{c347d6ae-7a2b-4ed0-97ad-2595e1c5d7dd}*SharedItemsImports = 4
Externals\crystaledit\editlib\editlibparsers.vcxitems*{c347d6ae-7a2b-4ed0-97ad-2595e1c5d7dd}*SharedItemsImports = 4
diff --git a/DownloadDeps.cmd b/DownloadDeps.cmd
index 4bc87996e36..7d606c55ddb 100644
--- a/DownloadDeps.cmd
+++ b/DownloadDeps.cmd
@@ -36,6 +36,9 @@ https://mirror.msys2.org/mingw/mingw32/mingw-w64-i686-md4c-0.5.2-1-any.pkg.tar.z
https://mirror.msys2.org/msys/i686/gcc-libs-10.2.0-1-i686.pkg.tar.zst!Build\msys2_tmp ^
https://mirror.msys2.org/msys/i686/msys2-runtime-3.2.0-14-i686.pkg.tar.zst!Build\msys2_tmp ^
https://mirror.msys2.org/msys/i686/patch-2.7.6-1-i686.pkg.tar.xz!Build\msys2_tmp ^
+https://mirror.msys2.org/msys/i686/lemon-3.46.1-1-i686.pkg.tar.zst!Build\msys2_tmp ^
+https://mirror.msys2.org/msys/i686/re2c-3.1-2-i686.pkg.tar.zst!Build\msys2_tmp ^
+https://mirror.msys2.org/msys/i686/gcc-libs-13.3.0-1-i686.pkg.tar.zst!Build\msys2_tmp ^
http://www.magicnotes.com/steelbytes/SBAppLocale_ENG.zip!Docs\Manual\Tools
pushd "%~dp0"
@@ -71,6 +74,10 @@ mkdir Build\msys2\usr\share 2> NUL
copy Build\msys2_tmp\usr\bin\patch.exe Build\msys2\usr\bin\
copy Build\msys2_tmp\usr\bin\msys-2.0.dll Build\msys2\usr\bin\
copy Build\msys2_tmp\usr\bin\msys-gcc_s-1.dll Build\msys2\usr\bin\
+copy Build\msys2_tmp\usr\bin\msys-gcc_s-seh-1.dll Build\msys2\usr\bin\
+copy "Build\msys2_tmp\usr\bin\msys-stdc++-6.dll" Build\msys2\usr\bin\
+copy Build\msys2_tmp\usr\bin\lemon.exe Build\msys2\usr\bin\
+copy Build\msys2_tmp\usr\bin\re2c.exe Build\msys2\usr\bin\
xcopy /s /y Build\msys2_tmp\usr\share\*.* Build\msys2\usr\share\
rmdir /q /s Build\msys2_tmp\ > NUL 2> NUL
diff --git a/Externals/crystaledit/icudata/icudata.vcxproj b/Externals/crystaledit/icudata/icudata.vcxproj
index 448a0f14114..3dc705667c0 100644
--- a/Externals/crystaledit/icudata/icudata.vcxproj
+++ b/Externals/crystaledit/icudata/icudata.vcxproj
@@ -39,7 +39,7 @@
Win32Proj
{2710a368-ed56-4fb1-80c3-d93ba6483710}
icudata
- 10.0.22000.0
+ 10.0.26100.0
$(LatestTargetPlatformVersion)
diff --git a/Externals/poco/Foundation/Foundation.vcxproj b/Externals/poco/Foundation/Foundation.vcxproj
index 43f7030aa72..f59bbef3dad 100644
--- a/Externals/poco/Foundation/Foundation.vcxproj
+++ b/Externals/poco/Foundation/Foundation.vcxproj
@@ -445,6 +445,15 @@
true
+
+ true
+
+
+ true
+
+
+ true
+
true
@@ -470,6 +479,9 @@
true
+
+ true
+
true
@@ -531,6 +543,9 @@
true
+
+ true
+
true
@@ -819,6 +834,8 @@
+
+
@@ -830,6 +847,7 @@
+
@@ -850,6 +868,7 @@
+
diff --git a/Externals/poco/Foundation/Foundation.vcxproj.filters b/Externals/poco/Foundation/Foundation.vcxproj.filters
index e808acec7ab..8a657ccf35a 100644
--- a/Externals/poco/Foundation/Foundation.vcxproj.filters
+++ b/Externals/poco/Foundation/Foundation.vcxproj.filters
@@ -553,6 +553,21 @@
Logging\Source Files
+
+ DateTime\Source Files
+
+
+ DateTime\Source Files
+
+
+ DateTime\Source Files
+
+
+ DateTime\Source Files
+
+
+ DateTime\Source Files
+
@@ -1050,5 +1065,17 @@
Logging\Header Files
+
+ DateTime\Header Files
+
+
+ DateTime\Header Files
+
+
+ DateTime\Header Files
+
+
+ DateTime\Header Files
+
\ No newline at end of file
diff --git a/Externals/poco/Foundation/src/Foundation.cpp b/Externals/poco/Foundation/src/Foundation.cpp
index c314451e446..51af50a0c2a 100644
--- a/Externals/poco/Foundation/src/Foundation.cpp
+++ b/Externals/poco/Foundation/src/Foundation.cpp
@@ -16,9 +16,14 @@
// DateTime
#include "Clock.cpp"
#include "DateTime.cpp"
+#include "DateTimeFormat.cpp"
+#include "DateTimeFormatter.cpp"
+#include "DateTimeParser.cpp"
+#include "LocalDateTime.cpp"
#include "Stopwatch.cpp"
#include "Timespan.cpp"
#include "Timestamp.cpp"
+#include "Timezone.cpp"
// Filesystem
#include "DirectoryIterator.cpp"
#include "File.cpp"
diff --git a/Src/DiffContext.cpp b/Src/DiffContext.cpp
index 90215cacf94..8f457f4a6b3 100644
--- a/Src/DiffContext.cpp
+++ b/Src/DiffContext.cpp
@@ -133,20 +133,14 @@ static bool CheckFileForVersion(const String& ext)
void CDiffContext::UpdateVersion(DIFFITEM &di, int nIndex) const
{
DiffFileInfo & dfi = di.diffFileInfo[nIndex];
- // Check only binary files
- dfi.version.SetFileVersionNone();
-
- if (di.diffcode.isDirectory())
+ if (!di.diffcode.exists(nIndex) || di.diffcode.isDirectory() || !CheckFileForVersion(paths::FindExtension(di.diffFileInfo[nIndex].filename)))
+ {
+ dfi.version.SetFileVersionNone();
return;
+ }
- String spath;
- if (!di.diffcode.exists(nIndex))
- return;
- String ext = paths::FindExtension(di.diffFileInfo[nIndex].filename);
- if (!CheckFileForVersion(ext))
- return;
- spath = di.getFilepath(nIndex, GetNormalizedPath(nIndex));
- spath = paths::ConcatPath(spath, di.diffFileInfo[nIndex].filename);
+ const String spath = paths::ConcatPath(
+ di.getFilepath(nIndex, GetNormalizedPath(nIndex)), di.diffFileInfo[nIndex].filename);
// Get version info if it exists
CVersionInfo ver(spath.c_str());
diff --git a/Src/DiffItem.cpp b/Src/DiffItem.cpp
index e0edaf3c5ca..ebd07f739f7 100644
--- a/Src/DiffItem.cpp
+++ b/Src/DiffItem.cpp
@@ -114,7 +114,7 @@ void DIFFITEM::Swap(int idx1, int idx2)
void DIFFITEM::ClearAllAdditionalProperties()
{
- const int n = ((diffcode.diffcode & DIFFCODE::THREEWAY) != 0) ? 3 : 2;
+ const int n = diffcode.isThreeway() ? 3 : 2;
for (int i = 0; i < n; ++i)
diffFileInfo[i].m_pAdditionalProperties.reset();
if (HasChildren())
@@ -223,7 +223,7 @@ void DIFFCODE::swap(int idx1, int idx2)
bool binflag2 = (diffcode & (BINSIDE1 << idx2));
Set(BINSIDE1 << idx1, binflag2 ? (BINSIDE1 << idx1) : 0);
Set(BINSIDE1 << idx2, binflag1 ? (BINSIDE1 << idx2) : 0);
- if ((diffcode & THREEWAY) != 0)
+ if (isThreeway())
{
int idx = -1;
switch (diffcode & COMPAREFLAGS3WAY)
diff --git a/Src/DiffItem.h b/Src/DiffItem.h
index c1890f59ede..6edc3b2c6f3 100644
--- a/Src/DiffItem.h
+++ b/Src/DiffItem.h
@@ -137,7 +137,11 @@ struct DIFFCODE
}
bool existAll() const
{
- return ((diffcode & DIFFCODE::THREEWAY) ? DIFFCODE::ALL : DIFFCODE::BOTH) == (diffcode & DIFFCODE::ALL);
+ return (isThreeway() ? DIFFCODE::ALL : DIFFCODE::BOTH) == (diffcode & DIFFCODE::ALL);
+ }
+ bool isThreeway() const
+ {
+ return (diffcode & DIFFCODE::THREEWAY) != 0;
}
// compare result
diff --git a/Src/DirActions.cpp b/Src/DirActions.cpp
index a39e961f6de..ed9bff292e9 100644
--- a/Src/DirActions.cpp
+++ b/Src/DirActions.cpp
@@ -1033,12 +1033,12 @@ int GetColImage(const DIFFITEM &di)
if (di.diffcode.isSideFirstOnly())
return (di.diffcode.isDirectory() ? DIFFIMG_LDIRUNIQUE : DIFFIMG_LUNIQUE);
if (di.diffcode.isSideSecondOnly())
- return ((di.diffcode.diffcode & DIFFCODE::THREEWAY) == 0 ?
+ return (!di.diffcode.isThreeway() ?
(di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE) :
(di.diffcode.isDirectory() ? DIFFIMG_MDIRUNIQUE : DIFFIMG_MUNIQUE));
if (di.diffcode.isSideThirdOnly())
return (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE);
- if ((di.diffcode.diffcode & DIFFCODE::THREEWAY) != 0)
+ if (di.diffcode.isThreeway())
{
if (!di.diffcode.exists(0))
return (di.diffcode.isDirectory() ? DIFFIMG_LDIRMISSING : DIFFIMG_LMISSING);
diff --git a/Src/DirDoc.cpp b/Src/DirDoc.cpp
index 3d69f6a2931..fed21afa524 100644
--- a/Src/DirDoc.cpp
+++ b/Src/DirDoc.cpp
@@ -32,6 +32,7 @@
#include "LineFiltersList.h"
#include "SubstitutionFiltersList.h"
#include "FileFilterHelper.h"
+#include "FilterErrorMessages.h"
#include "DirActions.h"
#include "DirScan.h"
#include "MessageBoxDialog.h"
@@ -246,7 +247,6 @@ void CDirDoc::InitDiffContext(CDiffContext *pCtxt)
if (m_pDirView)
pCtxt->m_pPropertySystem.reset(new PropertySystem(m_pDirView->GetDirViewColItems()->GetAdditionalPropertyNames()));
- m_imgfileFilter.UseMask(true);
m_imgfileFilter.SetMask(GetOptionsMgr()->GetString(OPT_CMP_IMG_FILEPATTERNS));
pCtxt->m_pImgfileFilter = &m_imgfileFilter;
@@ -257,9 +257,21 @@ void CDirDoc::InitDiffContext(CDiffContext *pCtxt)
pGlobalFileFilter->ReloadUpdatedFilters();
m_fileHelper.CloneFrom(pGlobalFileFilter);
pCtxt->m_piFilterGlobal = &m_fileHelper;
+ pCtxt->m_piFilterGlobal->SetDiffContext(pCtxt);
// All plugin management is done by our plugin manager
pCtxt->m_piPluginInfos = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED) ? &m_pluginman : nullptr;
+
+ CheckFilter();
+}
+
+void CDirDoc::CheckFilter()
+{
+ for (const auto* error: m_pCtxt->m_piFilterGlobal->GetErrorList())
+ {
+ String msg = FormatFilterErrorSummary(*error);
+ RootLogger::Error(msg);
+ }
}
/**
diff --git a/Src/DirDoc.h b/Src/DirDoc.h
index 07243849253..976159c04a0 100644
--- a/Src/DirDoc.h
+++ b/Src/DirDoc.h
@@ -132,6 +132,7 @@ class CDirDoc : public CDocument, public IMDITab, public IDirDoc
void InitDiffContext(CDiffContext *pCtxt);
void LoadLineFilterList(CDiffContext *pCtxt);
void LoadSubstitutionFiltersList(CDiffContext* pCtxt);
+ void CheckFilter();
// Generated message map functions
//{{AFX_MSG(CDirDoc)
diff --git a/Src/DirScan.cpp b/Src/DirScan.cpp
index def84e6a5f2..a14d855ab8e 100644
--- a/Src/DirScan.cpp
+++ b/Src/DirScan.cpp
@@ -242,16 +242,6 @@ int DirScan_GetItems(const PathContext &paths, const String subdir[],
{
leftnewsub = (nDiffCode & DIFFCODE::FIRST) ? subprefix[0] + dirs[0][i].filename.get() : subprefix[0] + dirs[1][j].filename.get();
rightnewsub = (nDiffCode & DIFFCODE::SECOND) ? subprefix[1] + dirs[1][j].filename.get() : subprefix[1] + dirs[0][i].filename.get();
-
- // Test against filter so we don't include contents of filtered out directories
- // Also this is only place we can test for both-sides directories in recursive compare
- if ((pCtxt->m_piFilterGlobal!=nullptr && !pCtxt->m_piFilterGlobal->includeDir(leftnewsub, rightnewsub)) ||
- (pCtxt->m_bIgnoreReparsePoints && (
- (nDiffCode & DIFFCODE::FIRST) && (dirs[0][i].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
- (nDiffCode & DIFFCODE::SECOND) && (dirs[1][j].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT))
- )
- )
- nDiffCode |= DIFFCODE::SKIPPED;
}
else
{
@@ -267,17 +257,6 @@ int DirScan_GetItems(const PathContext &paths, const String subdir[],
if (nDiffCode & DIFFCODE::THIRD) rightnewsub += dirs[2][k].filename;
else if (nDiffCode & DIFFCODE::FIRST) rightnewsub += dirs[0][i].filename;
else if (nDiffCode & DIFFCODE::SECOND) rightnewsub += dirs[1][j].filename;
-
- // Test against filter so we don't include contents of filtered out directories
- // Also this is only place we can test for both-sides directories in recursive compare
- if ((pCtxt->m_piFilterGlobal!=nullptr && !pCtxt->m_piFilterGlobal->includeDir(leftnewsub, middlenewsub, rightnewsub)) ||
- (pCtxt->m_bIgnoreReparsePoints && (
- (nDiffCode & DIFFCODE::FIRST) && (dirs[0][i].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
- (nDiffCode & DIFFCODE::SECOND) && (dirs[1][j].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
- (nDiffCode & DIFFCODE::THIRD) && (dirs[2][k].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT))
- )
- )
- nDiffCode |= DIFFCODE::SKIPPED;
}
// add to list
@@ -305,7 +284,7 @@ int DirScan_GetItems(const PathContext &paths, const String subdir[],
(nDiffCode & DIFFCODE::FIRST ) ? &dirs[0][i] : nullptr,
(nDiffCode & DIFFCODE::SECOND) ? &dirs[1][j] : nullptr,
nDiffCode, myStruct, parent);
- if ((nDiffCode & DIFFCODE::SKIPPED) == 0 && ((nDiffCode & DIFFCODE::SIDEFLAGS) == DIFFCODE::BOTH || bUniques))
+ if ((me->diffcode.diffcode & DIFFCODE::SKIPPED) == 0 && ((nDiffCode & DIFFCODE::SIDEFLAGS) == DIFFCODE::BOTH || bUniques))
{
// Scan recursively all subdirectories too, we are not adding folders
String newsubdir[3] = {leftnewsub, rightnewsub};
@@ -322,7 +301,7 @@ int DirScan_GetItems(const PathContext &paths, const String subdir[],
(nDiffCode & DIFFCODE::SECOND) ? &dirs[1][j] : nullptr,
(nDiffCode & DIFFCODE::THIRD ) ? &dirs[2][k] : nullptr,
nDiffCode, myStruct, parent);
- if ((nDiffCode & DIFFCODE::SKIPPED) == 0 && ((nDiffCode & DIFFCODE::SIDEFLAGS) == DIFFCODE::ALL || bUniques))
+ if ((me->diffcode.diffcode & DIFFCODE::SKIPPED) == 0 && ((nDiffCode & DIFFCODE::SIDEFLAGS) == DIFFCODE::ALL || bUniques))
{
// Scan recursively all subdirectories too, we are not adding folders
String newsubdir[3] = {leftnewsub, middlenewsub, rightnewsub};
@@ -873,16 +852,7 @@ static void CompareDiffItem(FolderCmp &fc, DIFFITEM &di)
else
{
// 1. Test against filters
- if (pCtxt->m_piFilterGlobal==nullptr ||
- (nDirs == 2 && pCtxt->m_piFilterGlobal->includeFile(
- paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename),
- paths::ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename)
- )) ||
- (nDirs == 3 && pCtxt->m_piFilterGlobal->includeFile(
- paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename),
- paths::ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename),
- paths::ConcatPath(di.diffFileInfo[2].path, di.diffFileInfo[2].filename)
- )))
+ if (pCtxt->m_piFilterGlobal==nullptr || pCtxt->m_piFilterGlobal->includeFile(di))
{
di.diffcode.diffcode |= DIFFCODE::INCLUDED;
di.diffcode.diffcode |= fc.prepAndCompareFiles(di);
@@ -990,10 +960,32 @@ static DIFFITEM *AddToList(const String& sDir1, const String& sDir2, const Strin
di->diffFileInfo[2].filename = ent2->filename;
}
+ CDiffContext *pCtxt = myStruct->context;
+
+ // Test against filter so we don't include contents of filtered out directories
+ // Also this is only place we can test for both-sides directories in recursive compare
+ if ((pCtxt->m_piFilterGlobal != nullptr && !pCtxt->m_piFilterGlobal->includeDir(*di)))
+ code |= DIFFCODE::SKIPPED;
+
if (nItems == 2)
+ {
+ if (pCtxt->m_bIgnoreReparsePoints && (
+ (code & DIFFCODE::FIRST) && (di->diffFileInfo[0].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
+ (code & DIFFCODE::SECOND) && (di->diffFileInfo[1].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT))
+ )
+ code |= DIFFCODE::SKIPPED;
di->diffcode.diffcode = code;
+ }
else
+ {
+ if (pCtxt->m_bIgnoreReparsePoints && (
+ (code & DIFFCODE::FIRST) && (di->diffFileInfo[0].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
+ (code & DIFFCODE::SECOND) && (di->diffFileInfo[1].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
+ (code & DIFFCODE::THIRD) && (di->diffFileInfo[2].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT))
+ )
+ code |= DIFFCODE::SKIPPED;
di->diffcode.diffcode = code | DIFFCODE::THREEWAY;
+ }
if (!myStruct->bMarkedRescan && myStruct->m_fncCollect)
{
diff --git a/Src/DirView.cpp b/Src/DirView.cpp
index 1a06e37b264..b0bd109c56d 100644
--- a/Src/DirView.cpp
+++ b/Src/DirView.cpp
@@ -4401,68 +4401,6 @@ void CDirView::GetColors (int nRow, int nCol, COLORREF& clrBk, COLORREF& clrText
}
}
-void CDirView::OnSearch()
-{
- CDirDoc *pDoc = GetDocument();
- m_pList->SetRedraw(FALSE); // Turn off updating (better performance)
- int nRows = m_pList->GetItemCount();
- CDiffContext& ctxt = GetDiffContext();
-
- for (int currRow = nRows - 1; currRow >= 0; currRow--)
- {
- DIFFITEM *pos = GetItemKey(currRow);
- if (IsDiffItemSpecial(pos))
- continue;
-
- bool bFound = false;
- DIFFITEM &di = GetDiffItem(currRow);
- PathContext paths;
-
- for (int i = 0; i < pDoc->m_nDirs; i++)
- {
- if (di.diffcode.exists(i) && !di.diffcode.isDirectory())
- {
- GetItemFileNames(currRow, &paths);
- UniMemFile ufile;
- if (!ufile.OpenReadOnly(paths[i]))
- continue;
-
- ufile.SetUnicoding(di.diffFileInfo[i].encoding.m_unicoding);
- ufile.SetBom(di.diffFileInfo[i].encoding.m_bom);
- ufile.SetCodepage(di.diffFileInfo[i].encoding.m_codepage);
-
- ufile.ReadBom();
-
- String line;
- for (;;)
- {
- bool lossy = false;
- if (!ufile.ReadString(line, &lossy))
- break;
-
- if (tc::tcsstr(line.c_str(), _T("DirView")))
- {
- bFound = true;
- break;
- }
- }
-
- ufile.Close();
- if (bFound)
- break;
- }
- }
- if (!bFound)
- {
- String hiddden_item_path = di.getItemRelativePath();
- SetItemViewFlag(di, ViewCustomFlags::HIDDEN, ViewCustomFlags::VISIBILITY);
- DeleteItem(currRow);
- ctxt.m_vCurrentlyHiddenItems.push_back(hiddden_item_path);
- }
- }
- m_pList->SetRedraw(TRUE); // Turn updating back on
-}
-
/**
* @brief Drag files/directories from folder compare listing view.
*/
diff --git a/Src/DirView.h b/Src/DirView.h
index 8725e5d65a3..95e9a9b38c1 100644
--- a/Src/DirView.h
+++ b/Src/DirView.h
@@ -401,7 +401,6 @@ class CDirView : public CListView
afx_msg void OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnODFindItem(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
- afx_msg void OnSearch();
afx_msg void OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnStatusBarClick(NMHDR* pNMHDR, LRESULT* pResult);
diff --git a/Src/FileFilter.cpp b/Src/FileFilter.cpp
index 7549c2040b4..5f61d6cf331 100644
--- a/Src/FileFilter.cpp
+++ b/Src/FileFilter.cpp
@@ -7,7 +7,13 @@
#include "pch.h"
#include "FileFilter.h"
+#include "FilterEngine/FilterExpression.h"
+#include "DiffItem.h"
+#include "unicoder.h"
+#include "paths.h"
#include
+#include
+#include
using std::vector;
@@ -20,6 +26,51 @@ FileFilter::~FileFilter()
EmptyFilterList(&filefiltersExclude);
EmptyFilterList(&dirfilters);
EmptyFilterList(&dirfiltersExclude);
+ EmptyExpressionList(&fileExpressionFilters);
+ EmptyExpressionList(&fileExpressionFiltersExclude);
+ EmptyExpressionList(&dirExpressionFilters);
+ EmptyExpressionList(&dirExpressionFiltersExclude);
+}
+
+/**
+ * @brief Add a single pattern (if nonempty & valid) to a pattern list.
+ *
+ * @param [in] filterList List where pattern is added.
+ * @param [in] str Temporary variable (ie, it may be altered)
+ * @param [in] lineNumber Line number in filter file, used for error reporting.
+ */
+void FileFilter::AddFilterPattern(vector* filterList, const String& str, bool fileFilter, int lineNumber)
+{
+ int re_opts = Poco::RegularExpression::RE_CASELESS;
+ std::string regexString = ucr::toUTF8(str);
+ re_opts |= Poco::RegularExpression::RE_UTF8;
+ try
+ {
+ filterList->push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts, fileFilter)));
+ }
+ catch (const Poco::RegularExpressionException& e)
+ {
+ errors.emplace_back(FILTER_ERROR_INVALID_REGULAR_EXPRESSION, lineNumber, -1, str, e.message());
+ }
+}
+
+/**
+ * @brief Add a single expression (if nonempty & valid) to a expression list.
+ *
+ * @param [in] filterList List where expression is added.
+ * @param [in] str Temporary variable (ie, it may be altered)
+ * @param [in] lineNumber Line number in filter file, used for error reporting.
+*/
+void FileFilter::AddFilterExpression(vector* filterList, const String& str, int lineNumber)
+{
+ String str2 = strutils::trim_ws(str);
+ std::shared_ptr pExpression(new FilterExpression(ucr::toUTF8(str)));
+ if (pExpression->errorCode != 0)
+ {
+ errors.emplace_back(pExpression->errorCode, lineNumber, pExpression->errorPosition, str2, pExpression->errorMessage);
+ return;
+ }
+ filterList->emplace_back(pExpression);
}
/**
@@ -32,6 +83,16 @@ void FileFilter::EmptyFilterList(vector *filterList)
filterList->clear();
}
+/**
+ * @brief Deletes items from expression list.
+ *
+ * @param [in] expressionList List to empty.
+ */
+void FileFilter::EmptyExpressionList(vector *expressionList)
+{
+ expressionList->clear();
+}
+
/**
* @brief Clone file filter from another filter.
* This function clones file filter from another filter.
@@ -63,6 +124,23 @@ void FileFilter::CloneFrom(const FileFilter* filter)
{
dirfilters.emplace_back(std::make_shared(filter->dirfilters[i].get()));
}
+
+ fileExpressionFilters.clear();
+ count = filter->fileExpressionFilters.size();
+ fileExpressionFilters.reserve(count);
+ for (size_t i = 0; i < count; i++)
+ {
+ fileExpressionFilters.emplace_back(std::make_shared(*filter->fileExpressionFilters[i].get()));
+ }
+
+ dirExpressionFilters.clear();
+ count = filter->dirExpressionFilters.size();
+ dirExpressionFilters.reserve(count);
+ for (size_t i = 0; i < count; i++)
+ {
+ dirExpressionFilters.emplace_back(std::make_shared(*filter->dirExpressionFilters[i].get()));
+ }
+
filefiltersExclude.clear();
count = filter->filefiltersExclude.size();
filefiltersExclude.reserve(count);
@@ -78,4 +156,223 @@ void FileFilter::CloneFrom(const FileFilter* filter)
{
dirfiltersExclude.emplace_back(std::make_shared(filter->dirfiltersExclude[i].get()));
}
+
+ fileExpressionFiltersExclude.clear();
+ count = filter->fileExpressionFiltersExclude.size();
+ fileExpressionFiltersExclude.reserve(count);
+ for (size_t i = 0; i < count; i++)
+ {
+ fileExpressionFiltersExclude.emplace_back(std::make_shared(*filter->fileExpressionFiltersExclude[i].get()));
+ }
+
+ dirExpressionFiltersExclude.clear();
+ count = filter->dirExpressionFiltersExclude.size();
+ dirExpressionFiltersExclude.reserve(count);
+ for (size_t i = 0; i < count; i++)
+ {
+ dirExpressionFiltersExclude.emplace_back(std::make_shared(*filter->dirExpressionFiltersExclude[i].get()));
+ }
+
+ errors = filter->errors;
+}
+
+/**
+ * @brief Test given string against given regexp list.
+ *
+ * @param [in] filterList List of regexps to test against.
+ * @param [in] szTest String to test against regexps.
+ * @return true if string passes
+ * @note Matching stops when first match is found.
+ */
+bool TestAgainstRegList(const vector* filterList, const String& szTest)
+{
+ if (filterList->size() == 0)
+ return false;
+
+ std::string compString, compStringFileName;
+ ucr::toUTF8(szTest, compString);
+ vector::const_iterator iter = filterList->begin();
+ while (iter != filterList->end())
+ {
+ Poco::RegularExpression::Match match;
+ try
+ {
+ if ((*iter)->_fileNameOnly && compStringFileName.empty())
+ ucr::toUTF8(paths::FindFileName(szTest), compStringFileName);
+ if ((*iter)->regexp.match((*iter)->_fileNameOnly ? compStringFileName : compString, 0, match) > 0)
+ return true;
+ }
+ catch (...)
+ {
+ // TODO:
+ }
+
+ ++iter;
+ }
+ return false;
+}
+
+/**
+ * @brief Test given DIFFITEM against given regexp list.
+ * @param [in] filterList List of regexps to test against.
+ * @param [in] di DIFFITEM to test against regexps.
+ * @return true if DIFFITEM passes
+ * @note Matching stops when first match is found.
+ */
+bool FileFilter::TestAgainstRegList(const vector* filterList, const DIFFITEM& di)
+{
+ if (filterList->size() == 0)
+ return false;
+
+ const int nDirs = di.diffcode.isThreeway() ? 3 : 2;
+ int i = 0;
+ for (; i < nDirs; ++i)
+ {
+ if (!di.diffFileInfo[i].filename.get().empty())
+ break;
+ }
+ if (i >= nDirs)
+ return false;
+
+ const String& szTest = (di.diffFileInfo[i].IsDirectory() ? _T("\\") : _T("")) + paths::ConcatPath(di.diffFileInfo[i].path, di.diffFileInfo[i].filename);
+ std::string compString, compStringFileName;
+ ucr::toUTF8(szTest, compString);
+ vector::const_iterator iter = filterList->begin();
+ while (iter != filterList->end())
+ {
+ Poco::RegularExpression::Match match;
+ try
+ {
+ if ((*iter)->_fileNameOnly && compStringFileName.empty())
+ ucr::toUTF8(paths::FindFileName(szTest), compStringFileName);
+ if ((*iter)->regexp.match((*iter)->_fileNameOnly ? compStringFileName : compString, 0, match) > 0)
+ return true;
+ }
+ catch (...)
+ {
+ // TODO:
+ }
+
+ ++iter;
+ }
+ return false;
}
+
+/**
+ * @brief Test given DIFFITEM against given expression list.
+ * @param [in] filterList List of expressions to test against.
+ * @param [in] di DIFFITEM to test against regexps.
+ * @return true if DIFFITEM passes
+ * @note Matching stops when first match is found.
+ */
+bool FileFilter::TestAgainstExpressionList(const vector* filterList, const DIFFITEM& di)
+{
+ if (filterList->size() == 0)
+ return false;
+
+ for (const auto& filter : *filterList)
+ {
+ if (filter->Evaluate(di))
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * @brief Test given filename against filefilter.
+ *
+ * Test filename against active filefilter. If matching rule is found
+ * we must first determine type of rule that matched. If we return false
+ * from this function directory scan marks file as skipped.
+ *
+ * @param [in] szFileName Filename to test
+ * @return true if file passes the filter
+ */
+bool FileFilter::TestFileNameAgainstFilter(const String& szFileName) const
+{
+ if (::TestAgainstRegList(&filefilters, szFileName))
+ {
+ if (filefiltersExclude.empty() || !::TestAgainstRegList(&filefiltersExclude, szFileName))
+ return !default_include;
+ }
+ return default_include;
+}
+
+/**
+ * @brief Set diff context for all filters in the given file filter.
+ * @param [in] pDiffContext Pointer to diff context to set for all filters.
+ */
+void FileFilter::SetDiffContext(const CDiffContext* pDiffContext)
+{
+ for (auto& filters :
+ { fileExpressionFilters, fileExpressionFiltersExclude, dirExpressionFilters, dirExpressionFiltersExclude })
+ {
+ for (const auto& filter : filters)
+ filter->SetDiffContext(pDiffContext);
+ }
+}
+
+/**
+ * @brief Test given DIFFITEM against filefilter.
+ * @param [in] di DIFFITEM to test
+ * @return true if DIFFITEM passes the filter
+ */
+bool FileFilter::TestFileDiffItemAgainstFilter(const DIFFITEM& di) const
+{
+ bool matched = TestAgainstRegList(&filefilters, di);
+ if (!matched && TestAgainstExpressionList(&fileExpressionFilters, di))
+ matched = true;
+ if (matched)
+ {
+ matched = !TestAgainstRegList(&filefiltersExclude, di);
+ if (matched)
+ matched = !TestAgainstExpressionList(&fileExpressionFiltersExclude, di);
+ }
+ if (matched)
+ return !default_include;
+ return default_include;
+}
+
+/**
+ * @brief Test given directory name against filefilter.
+ *
+ * Test directory name against active filefilter. If matching rule is found
+ * we must first determine type of rule that matched. If we return false
+ * from this function directory scan marks file as skipped.
+ *
+ * @param [in] szDirName Directory name to test
+ * @return true if directory name passes the filter
+ */
+bool FileFilter::TestDirNameAgainstFilter(const String& szDirName) const
+{
+ if (::TestAgainstRegList(&dirfilters, szDirName))
+ {
+ if (dirfiltersExclude.empty() || !::TestAgainstRegList(&dirfiltersExclude, szDirName))
+ return !default_include;
+ }
+ return default_include;
+}
+
+/**
+ * @brief Test given DIFFITEM against filefilter.
+ * @param [in] di DIFFITEM to test
+ * @return true if DIFFITEM passes the filter
+ */
+bool FileFilter::TestDirDiffItemAgainstFilter(const DIFFITEM& di) const
+{
+ bool matched = TestAgainstRegList(&dirfilters, di);
+ if (!matched && TestAgainstExpressionList(&dirExpressionFilters, di))
+ matched = true;
+ if (matched)
+ {
+ matched = !TestAgainstRegList(&dirfiltersExclude, di);
+ if (matched)
+ matched = !TestAgainstExpressionList(&dirExpressionFiltersExclude, di);
+ }
+ if (matched)
+ return !default_include;
+ return default_include;
+}
+
+
diff --git a/Src/FileFilter.h b/Src/FileFilter.h
index ceb33db5199..358c760d423 100644
--- a/Src/FileFilter.h
+++ b/Src/FileFilter.h
@@ -6,12 +6,17 @@
*/
#pragma once
+#include "FilterError.h"
#include
#include
#define POCO_NO_UNWINDOWS 1
#include
#include "UnicodeString.h"
+struct FilterExpression;
+class CDiffContext;
+class DIFFITEM;
+
/**
* @brief FileFilter rule.
*
@@ -40,7 +45,21 @@ struct FileFilterElement
}
};
+struct FileFilterErrorInfo
+{
+ int line; /**< Line number in filter file where error occurred */
+ FilterErrorCode errorCode; /**< Error code, see FilterErrorCode enum for values */
+ int errorPosition; /**< Position in line where error occurred, if applicable */
+ String srcText; /**< Source text of the line where error occurred, if applicable */
+ std::string errorText; /**< Text describing the error, if applicable */
+ FileFilterErrorInfo(FilterErrorCode code, int lineNumber, int position, const String& src, const std::string& msg) :
+ errorCode(code), line(lineNumber), errorPosition(position), srcText(src), errorText(msg)
+ {
+ }
+};
+
typedef std::shared_ptr FileFilterElementPtr;
+typedef std::shared_ptr FilterExpressionPtr;
/**
* @brief One actual filter.
@@ -61,11 +80,27 @@ struct FileFilter
std::vector filefiltersExclude; /**< List of rules for files (exclude) */
std::vector dirfilters; /**< List of rules for directories */
std::vector dirfiltersExclude; /**< List of rules for directories (exclude) */
+ std::vector fileExpressionFilters; /**< List of file filter expressions */
+ std::vector fileExpressionFiltersExclude; /**< List of file filter expressions (exclude) */
+ std::vector dirExpressionFilters; /**< List of dir filter expressions */
+ std::vector dirExpressionFiltersExclude; /**< List of dir filter expressions (exclude) */
+ std::vector errors; /**< List of errors in filter file */
FileFilter() : default_include(true) { }
~FileFilter();
+ void AddFilterPattern(std::vector* filterList, const String& str, bool fileFilter, int lineNumber);
+ void AddFilterExpression(std::vector* filterList, const String& str, int lineNumber);
static void EmptyFilterList(std::vector *filterList);
+ static void EmptyExpressionList(std::vector *filterList);
void CloneFrom(const FileFilter* filter);
+ // methods to actually use filter
+ bool TestFileNameAgainstFilter(const String& szFileName) const;
+ void SetDiffContext(const CDiffContext* pDiffContext);
+ bool TestFileDiffItemAgainstFilter(const DIFFITEM& di) const;
+ bool TestDirNameAgainstFilter(const String& szDirName) const;
+ bool TestDirDiffItemAgainstFilter(const DIFFITEM& di) const;
+ static bool TestAgainstRegList(const std::vector* filterList, const DIFFITEM& di);
+ static bool TestAgainstExpressionList(const std::vector* filterList, const DIFFITEM& di);
};
typedef std::shared_ptr FileFilterPtr;
diff --git a/Src/FileFilterHelper.cpp b/Src/FileFilterHelper.cpp
index e7844406060..666d72aa9ef 100644
--- a/Src/FileFilterHelper.cpp
+++ b/Src/FileFilterHelper.cpp
@@ -7,25 +7,24 @@
#include "pch.h"
#include "FileFilterHelper.h"
+#include "FilterExpression.h"
#include "UnicodeString.h"
#include "FilterList.h"
-#include "DirItem.h"
+#include "DiffItem.h"
#include "FileFilterMgr.h"
#include "paths.h"
#include "Environment.h"
#include "unicoder.h"
-using std::vector;
-
/**
* @brief Constructor, creates new filtermanager.
*/
FileFilterHelper::FileFilterHelper()
: m_pMaskFileFilter(nullptr)
+, m_pMaskFileFilterExclude(nullptr)
, m_pMaskDirFilter(nullptr)
-, m_bUseMask(true)
+, m_pMaskDirFilterExclude(nullptr)
, m_fileFilterMgr(new FileFilterMgr)
-, m_currentFilter(nullptr)
{
}
@@ -34,37 +33,12 @@ FileFilterHelper::FileFilterHelper()
*/
FileFilterHelper::~FileFilterHelper() = default;
-/**
- * @brief Store current filter path.
- *
- * Select filter based on filepath. If filter with that path
- * is found select it. Otherwise set path to empty (default).
- * @param [in] szFileFilterPath Full path to filter to select.
- */
-void FileFilterHelper::SetFileFilterPath(const String& szFileFilterPath)
-{
- // Use none as default path
- m_sFileFilterPath.clear();
-
- if (m_fileFilterMgr == nullptr)
- return;
-
- // Don't bother to lookup empty path
- if (!szFileFilterPath.empty())
- {
- m_currentFilter = m_fileFilterMgr->GetFilterByPath(szFileFilterPath);
- if (m_currentFilter != nullptr)
- m_sFileFilterPath = szFileFilterPath;
- }
-}
-
/**
* @brief Get list of filters currently available.
*
- * @param [out] selected Filepath of currently selected filter.
* @return Filter list to receive found filters.
*/
-std::vector FileFilterHelper::GetFileFilters(String & selected) const
+std::vector FileFilterHelper::GetFileFilters() const
{
std::vector filters;
if (m_fileFilterMgr != nullptr)
@@ -80,7 +54,6 @@ std::vector FileFilterHelper::GetFileFilters(String & selected)
filters.push_back(filter);
}
}
- selected = m_sFileFilterPath;
return filters;
}
@@ -92,10 +65,9 @@ std::vector FileFilterHelper::GetFileFilters(String & selected)
*/
String FileFilterHelper::GetFileFilterName(const String& filterPath) const
{
- String selected;
String name;
- vector filters = GetFileFilters(selected);
- vector::const_iterator iter = filters.begin();
+ std::vector filters = GetFileFilters();
+ std::vector::const_iterator iter = filters.begin();
while (iter != filters.end())
{
if ((*iter).fullpath == filterPath)
@@ -115,10 +87,9 @@ String FileFilterHelper::GetFileFilterName(const String& filterPath) const
*/
String FileFilterHelper::GetFileFilterPath(const String& filterName) const
{
- String selected;
String path;
- vector filters = GetFileFilters(selected);
- vector::const_iterator iter = filters.begin();
+ std::vector filters = GetFileFilters();
+ std::vector::const_iterator iter = filters.begin();
while (iter != filters.end())
{
if ((*iter).name == filterName)
@@ -141,57 +112,68 @@ void FileFilterHelper::SetUserFilterPath(const String & filterPath)
paths::normalize(m_sUserSelFilterPath);
}
-/**
- * @brief Select between mask and filterfile.
- * @param [in] bUseMask If true we use mask instead of filter files.
- */
-void FileFilterHelper::UseMask(bool bUseMask)
-{
- m_bUseMask = bUseMask;
- if (m_bUseMask)
- {
- if (m_pMaskFileFilter == nullptr)
- {
- m_pMaskFileFilter.reset(new FilterList);
- }
- if (m_pMaskDirFilter == nullptr)
- {
- m_pMaskDirFilter.reset(new FilterList);
- }
- }
- else
- {
- m_pMaskFileFilter.reset();
- m_pMaskDirFilter.reset();
- }
-}
-
/**
* @brief Set filemask for filtering.
* @param [in] strMask Mask to set (e.g. *.cpp;*.h).
*/
void FileFilterHelper::SetMask(const String& strMask)
{
- if (!m_bUseMask)
- {
- throw "Filter mask tried to set when masks disabled!";
- }
- m_sMask = strMask;
- auto [regExpFile, regExpFileExclude, regExpDir, regExpDirExclude] = ParseExtensions(strMask);
+ String flt = strutils::trim_ws(strMask);
+ String path = GetFileFilterPath(flt);
+ if (!path.empty())
+ flt = _T("fp:") + flt;
+
+ m_sMask = flt;
+ auto [regExpFile, regExpFileExclude, regExpDir, regExpDirExclude, pRegexOrExpressionFilter, pRegexOrExpressionFilterExclude]
+ = ParseExtensions(strMask);
std::string regexp_str_file = ucr::toUTF8(regExpFile);
std::string regexp_str_file_excluded = ucr::toUTF8(regExpFileExclude);
std::string regexp_str_dir = ucr::toUTF8(regExpDir);
std::string regexp_str_dir_excluded = ucr::toUTF8(regExpDirExclude);
- m_pMaskFileFilter->RemoveAllFilters();
- m_pMaskFileFilter->AddRegExp(regexp_str_file, false);
+ if (m_pMaskFileFilter)
+ m_pMaskFileFilter->RemoveAllFilters();
+ if (m_pMaskDirFilter)
+ m_pMaskDirFilter->RemoveAllFilters();
+ if (m_pMaskFileFilterExclude)
+ m_pMaskFileFilterExclude->RemoveAllFilters();
+ if (m_pMaskDirFilterExclude)
+ m_pMaskDirFilterExclude->RemoveAllFilters();
+ if (!regexp_str_file.empty())
+ {
+ if (!m_pMaskFileFilter)
+ m_pMaskFileFilter = std::make_unique();
+ m_pMaskFileFilter->AddRegExp(regexp_str_file);
+ }
+ else
+ m_pMaskFileFilter.reset();
+ if (!regexp_str_dir.empty())
+ {
+ if (!m_pMaskDirFilter)
+ m_pMaskDirFilter = std::make_unique();
+ m_pMaskDirFilter->AddRegExp(regexp_str_dir);
+ }
+ else
+ m_pMaskDirFilter.reset();
if (!regexp_str_file_excluded.empty())
- m_pMaskFileFilter->AddRegExp(regexp_str_file_excluded, true);
- m_pMaskDirFilter->RemoveAllFilters();
- m_pMaskDirFilter->AddRegExp(regexp_str_dir, false);
+ {
+ if (!m_pMaskFileFilterExclude)
+ m_pMaskFileFilterExclude = std::make_unique();
+ m_pMaskFileFilterExclude->AddRegExp(regexp_str_file_excluded);
+ }
+ else
+ m_pMaskFileFilterExclude.reset();
if (!regexp_str_dir_excluded.empty())
- m_pMaskDirFilter->AddRegExp(regexp_str_dir_excluded, true);
+ {
+ if (!m_pMaskDirFilterExclude)
+ m_pMaskDirFilterExclude = std::make_unique();
+ m_pMaskDirFilterExclude->AddRegExp(regexp_str_dir_excluded);
+ }
+ else
+ m_pMaskDirFilterExclude.reset();
+ m_pRegexOrExpressionFilter = pRegexOrExpressionFilter;
+ m_pRegexOrExpressionFilterExclude = pRegexOrExpressionFilterExclude;
}
static String addPeriodIfNoExtension(const String& path)
@@ -225,6 +207,28 @@ static String addPeriodIfNoExtension(const String& path)
return ret;
}
+void FileFilterHelper::SetDiffContext(const CDiffContext* pCtxt)
+{
+ if (m_pRegexOrExpressionFilter)
+ m_pRegexOrExpressionFilter->SetDiffContext(pCtxt);
+ if (m_pRegexOrExpressionFilterExclude)
+ m_pRegexOrExpressionFilterExclude->SetDiffContext(pCtxt);
+}
+
+std::vector FileFilterHelper::GetErrorList() const
+{
+ std::vector list;
+ for (const auto* pfilter : { m_pRegexOrExpressionFilter.get(), m_pRegexOrExpressionFilterExclude.get() })
+ {
+ if (pfilter)
+ {
+ for (const auto& error : pfilter->errors)
+ list.push_back(&error);
+ }
+ }
+ return list;
+}
+
/**
* @brief Check if any of filefilter rules match to filename.
*
@@ -233,28 +237,74 @@ static String addPeriodIfNoExtension(const String& path)
*/
bool FileFilterHelper::includeFile(const String& szFileName) const
{
- if (m_bUseMask)
- {
- if (m_pMaskFileFilter == nullptr)
- {
- throw "Use mask set, but no filter rules for mask!";
- }
+ // preprend a backslash if there is none
+ String strFileName = strutils::makelower(szFileName);
+ if (strFileName.empty() || strFileName[0] != '\\')
+ strFileName = _T("\\") + strFileName;
+ // append a point if there is no extension
+ std::string strFileNameUtf8Period = ucr::toUTF8(addPeriodIfNoExtension(strFileName));
+ bool result = m_pMaskFileFilter && m_pMaskFileFilter->Match(strFileNameUtf8Period);
+ if (!result)
+ result = m_pRegexOrExpressionFilter && TestAgainstRegList(&m_pRegexOrExpressionFilter->filefilters, szFileName);
+ if (!result)
+ return false;
+ if (m_pMaskFileFilterExclude && m_pMaskFileFilterExclude->Match(strFileNameUtf8Period))
+ return false;
+ if (m_pRegexOrExpressionFilter && TestAgainstRegList(&m_pRegexOrExpressionFilter->filefiltersExclude, szFileName))
+ return false;
+ if (m_pRegexOrExpressionFilterExclude && !m_pRegexOrExpressionFilterExclude->TestFileNameAgainstFilter(szFileName))
+ return false;
+ return true;
+}
+bool FileFilterHelper::includeFile(const DIFFITEM& di) const
+{
+ const int nDirs = di.diffcode.isThreeway() ? 3 : 2;
+ int i = 0;
+ for (; i < nDirs; ++i)
+ {
+ if (!di.diffFileInfo[i].filename.get().empty())
+ break;
+ }
+ std::string strFileNameUtf8Period;
+ bool result = false;
+ if (i < nDirs)
+ {
+ String szFileName = paths::ConcatPath(di.diffFileInfo[i].path, di.diffFileInfo[i].filename);
// preprend a backslash if there is none
String strFileName = strutils::makelower(szFileName);
if (strFileName.empty() || strFileName[0] != '\\')
strFileName = _T("\\") + strFileName;
// append a point if there is no extension
- strFileName = addPeriodIfNoExtension(strFileName);
-
- return m_pMaskFileFilter->Match(ucr::toUTF8(strFileName));
+ strFileNameUtf8Period = ucr::toUTF8(addPeriodIfNoExtension(strFileName));
+ result = m_pMaskFileFilter && m_pMaskFileFilter->Match(strFileNameUtf8Period);
}
- else
+ if (!result)
+ {
+ if (m_pRegexOrExpressionFilter)
+ {
+ result = FileFilter::TestAgainstRegList(&m_pRegexOrExpressionFilter->filefilters, di);
+ if (!result)
+ result = FileFilter::TestAgainstExpressionList(&m_pRegexOrExpressionFilter->fileExpressionFilters, di);
+ }
+ }
+ if (!result)
+ return false;
+ if (i < nDirs)
+ {
+ if (m_pMaskFileFilterExclude && m_pMaskFileFilterExclude->Match(strFileNameUtf8Period))
+ return false;
+ }
+ if (m_pRegexOrExpressionFilter)
{
- if (m_fileFilterMgr == nullptr || m_currentFilter ==nullptr)
- return true;
- return m_fileFilterMgr->TestFileNameAgainstFilter(m_currentFilter, szFileName);
+ if (FileFilter::TestAgainstRegList(&m_pRegexOrExpressionFilter->filefiltersExclude, di))
+ return false;
+ if (FileFilter::TestAgainstExpressionList(&m_pRegexOrExpressionFilter->fileExpressionFiltersExclude, di))
+ return false;
}
+ if (m_pRegexOrExpressionFilterExclude && !m_pRegexOrExpressionFilterExclude->TestFileDiffItemAgainstFilter(di))
+ return false;
+ return true;
}
/**
@@ -265,33 +315,78 @@ bool FileFilterHelper::includeFile(const String& szFileName) const
*/
bool FileFilterHelper::includeDir(const String& szDirName) const
{
- if (m_bUseMask)
+ // preprend a backslash if there is none
+ String strDirName = strutils::makelower(szDirName);
+ if (strDirName.empty() || strDirName[0] != '\\')
+ strDirName = _T("\\") + strDirName;
+ // append a point if there is no extension
+ std::string strDirNameUtf8Period = ucr::toUTF8(addPeriodIfNoExtension(strDirName));
+ bool result = m_pMaskDirFilter && m_pMaskDirFilter->Match(strDirNameUtf8Period);
+ if (!result)
{
- if (m_pMaskDirFilter == nullptr)
- {
- throw "Use mask set, but no filter rules for mask!";
- }
+ if (m_pRegexOrExpressionFilter)
+ result = TestAgainstRegList(&m_pRegexOrExpressionFilter->dirfilters, strDirName);
+ }
+ if (!result)
+ return false;
+ if (m_pMaskDirFilterExclude && m_pMaskDirFilterExclude->Match(strDirNameUtf8Period))
+ return false;
+ if (m_pRegexOrExpressionFilter && TestAgainstRegList(&m_pRegexOrExpressionFilter->dirfiltersExclude, strDirName))
+ return false;
+ if (m_pRegexOrExpressionFilterExclude && !m_pRegexOrExpressionFilterExclude->TestFileNameAgainstFilter(strDirName))
+ return false;
+ return true;
+}
+bool FileFilterHelper::includeDir(const DIFFITEM& di) const
+{
+ const int nDirs = di.diffcode.isThreeway() ? 3 : 2;
+ int i = 0;
+ for (; i < nDirs; ++i)
+ {
+ if (!di.diffFileInfo[i].filename.get().empty())
+ break;
+ }
+ std::string strDirNameUtf8Period;
+ bool result = false;
+ if (i < nDirs)
+ {
+ String szDirName = paths::ConcatPath(di.diffFileInfo[i].path, di.diffFileInfo[i].filename);
// preprend a backslash if there is none
String strDirName = strutils::makelower(szDirName);
if (strDirName.empty() || strDirName[0] != '\\')
strDirName = _T("\\") + strDirName;
// append a point if there is no extension
- strDirName = addPeriodIfNoExtension(strDirName);
-
- return m_pMaskDirFilter->Match(ucr::toUTF8(strDirName));
+ strDirNameUtf8Period = ucr::toUTF8(addPeriodIfNoExtension(strDirName));
+ result = m_pMaskDirFilter && m_pMaskDirFilter->Match(strDirNameUtf8Period);
}
- else
+ if (!result)
{
- if (m_fileFilterMgr == nullptr || m_currentFilter == nullptr)
- return true;
-
- // Add a backslash
- String strDirName(_T("\\"));
- strDirName += szDirName;
-
- return m_fileFilterMgr->TestDirNameAgainstFilter(m_currentFilter, strDirName);
+ if (m_pRegexOrExpressionFilter)
+ {
+ result = FileFilter::TestAgainstRegList(&m_pRegexOrExpressionFilter->dirfilters, di);
+ if (!result)
+ result = FileFilter::TestAgainstExpressionList(&m_pRegexOrExpressionFilter->dirExpressionFilters, di);
+ }
}
+ if (!result)
+ return false;
+ if (i < nDirs)
+ {
+ if (m_pMaskDirFilterExclude && m_pMaskDirFilterExclude->Match(strDirNameUtf8Period))
+ return false;
+ }
+ if (m_pRegexOrExpressionFilter)
+ {
+
+ if (FileFilter::TestAgainstRegList(&m_pRegexOrExpressionFilter->dirfiltersExclude, di))
+ return false;
+ if (FileFilter::TestAgainstExpressionList(&m_pRegexOrExpressionFilter->dirExpressionFiltersExclude, di))
+ return false;
+ }
+ if (m_pRegexOrExpressionFilterExclude && !m_pRegexOrExpressionFilterExclude->TestDirDiffItemAgainstFilter(di))
+ return false;
+ return true;
}
/**
@@ -330,12 +425,59 @@ static String ConvertWildcardPatternToRegexp(const String& pattern)
return _T("(^|\\\\)") + strRegex;
}
+static std::size_t findSeparator(const String& str, String& prefix, std::size_t startPos = 0)
+{
+ prefix.clear();
+ bool inQuotes = false;
+ bool allowOnlyBasicSeparators = false;
+ while (startPos < str.size() && str[startPos] == ' ')
+ ++startPos;
+ const String prefixes[] = { _T("f:"), _T("d:"), _T("f!:"), _T("d!:"), _T("fe:"), _T("de:"), _T("fe!:"), _T("de!:"), _T("fp:") };
+ for (const auto& pf : prefixes)
+ {
+ if (str.compare(startPos, pf.size(), pf) == 0)
+ {
+ startPos += pf.size();
+ allowOnlyBasicSeparators = true;
+ prefix = pf;
+ break;
+ }
+ }
+ for (std::size_t i = startPos; i < str.size(); ++i)
+ {
+ const auto ch = str[i];
+ if (ch == '"')
+ inQuotes = !inQuotes;
+ else if (!inQuotes &&
+ (ch == ';' || (!allowOnlyBasicSeparators && (ch == ',' || ch == '|' || ch == ':' || ch == ' '))))
+ return i;
+ }
+ return String::npos;
+}
+
+/**
+ * @brief Merge filter into regex or expression filter.
+ */
+static void mergeFilter(FileFilter* dest, const FileFilter* src)
+{
+ dest->filefilters.insert(dest->filefilters.end(), src->filefilters.begin(), src->filefilters.end());
+ dest->filefiltersExclude.insert(dest->filefiltersExclude.end(), src->filefiltersExclude.begin(), src->filefiltersExclude.end());
+ dest->dirfilters.insert(dest->dirfilters.end(), src->dirfilters.begin(), src->dirfilters.end());
+ dest->dirfiltersExclude.insert(dest->dirfiltersExclude.end(), src->dirfiltersExclude.begin(), src->dirfiltersExclude.end());
+ dest->fileExpressionFilters.insert(dest->fileExpressionFilters.end(), src->fileExpressionFilters.begin(), src->fileExpressionFilters.end());
+ dest->fileExpressionFiltersExclude.insert(dest->fileExpressionFiltersExclude.end(), src->fileExpressionFiltersExclude.begin(), src->fileExpressionFiltersExclude.end());
+ dest->dirExpressionFilters.insert(dest->dirExpressionFilters.end(), src->dirExpressionFilters.begin(), src->dirExpressionFilters.end());
+ dest->dirExpressionFiltersExclude.insert(dest->dirExpressionFiltersExclude.end(), src->dirExpressionFiltersExclude.begin(), src->dirExpressionFiltersExclude.end());
+ dest->errors.insert(dest->errors.end(), src->errors.begin(), src->errors.end());
+}
+
/**
* @brief Convert user-given extension list to valid regular expression.
* @param [in] Extension list/mask to convert to regular expression.
* @return Regular expression that matches extension list.
*/
-std::tuple FileFilterHelper::ParseExtensions(const String &extensions) const
+std::tuple, std::shared_ptr>
+FileFilterHelper::ParseExtensions(const String &extensions) const
{
String strFileParsed;
String strDirParsed;
@@ -344,19 +486,17 @@ std::tuple FileFilterHelper::ParseExtensions(con
std::vector dirPatterns;
std::vector dirPatternsExclude;
String ext(extensions);
- static const tchar_t pszSeps[] = _T(" ;|,:");
-
- ext += _T(";"); // Add one separator char to end
- size_t pos = ext.find_first_of(pszSeps);
-
- while (pos != String::npos)
+ String prefix;
+ std::shared_ptr pRegexOrExpressionFilter;
+ std::shared_ptr pRegexOrExpressionFilterExclude;
+ size_t pos = 0;
+ for (;;)
{
- String token = ext.substr(0, pos); // Get first extension
- ext = ext.substr(pos + 1); // Remove extension + separator
-
- // Only "*." or "*.something" allowed, other ignored
- if (token.length() >= 1)
+ pos = findSeparator(ext, prefix);
+ String token = ext.substr(0, pos == String::npos ? ext.size() : pos);
+ if (token.length() >= 1 && prefix.empty())
{
+ // Only "*." or "*.something" allowed, other ignored
bool exclude = token[0] == '!';
if (exclude)
token = token.substr(1);
@@ -380,21 +520,83 @@ std::tuple FileFilterHelper::ParseExtensions(con
filePatterns.push_back(strRegex);
}
}
-
- pos = ext.find_first_of(pszSeps);
+ else if (!prefix.empty())
+ {
+ if (!pRegexOrExpressionFilter)
+ {
+ pRegexOrExpressionFilter = std::make_shared();
+ pRegexOrExpressionFilter->default_include = false;
+ pRegexOrExpressionFilter->name = extensions;
+ }
+ token = strutils::trim_ws(token.substr(token.find(':') + 1));
+ if (prefix == _T("f:"))
+ pRegexOrExpressionFilter->AddFilterPattern(
+ &pRegexOrExpressionFilter->filefilters, token, true, 0);
+ else if (prefix == _T("f!:"))
+ pRegexOrExpressionFilter->AddFilterPattern(
+ &pRegexOrExpressionFilter->filefiltersExclude, token, true, 0);
+ else if (prefix == _T("d:"))
+ pRegexOrExpressionFilter->AddFilterPattern(
+ &pRegexOrExpressionFilter->dirfilters, token, false, 0);
+ else if (prefix == _T("d!:"))
+ pRegexOrExpressionFilter->AddFilterPattern(
+ &pRegexOrExpressionFilter->dirfiltersExclude, token, false, 0);
+ else if (prefix == _T("fe:"))
+ pRegexOrExpressionFilter->AddFilterExpression(
+ &pRegexOrExpressionFilter->fileExpressionFilters, token, 0);
+ else if (prefix == _T("fe!:"))
+ pRegexOrExpressionFilter->AddFilterExpression(
+ &pRegexOrExpressionFilter->fileExpressionFiltersExclude, token, 0);
+ else if (prefix == _T("de:"))
+ pRegexOrExpressionFilter->AddFilterExpression(
+ &pRegexOrExpressionFilter->dirExpressionFilters, token, 0);
+ else if (prefix == _T("de!:"))
+ pRegexOrExpressionFilter->AddFilterExpression(
+ &pRegexOrExpressionFilter->dirExpressionFiltersExclude, token, 0);
+ else if (prefix == _T("fp:"))
+ {
+ const String path = GetFileFilterPath(token);
+ if (!path.empty())
+ {
+ const FileFilter* filter = m_fileFilterMgr->GetFilterByPath(path);
+ if (filter)
+ {
+ if (!filter->default_include)
+ mergeFilter(pRegexOrExpressionFilter.get(), filter);
+ else
+ {
+ if (!pRegexOrExpressionFilterExclude)
+ {
+ pRegexOrExpressionFilterExclude = std::make_shared();
+ pRegexOrExpressionFilterExclude->default_include = true;
+ pRegexOrExpressionFilterExclude->name = extensions;
+ }
+ mergeFilter(pRegexOrExpressionFilterExclude.get(), filter);
+ }
+ }
+ }
+ else
+ {
+ pRegexOrExpressionFilter->errors.emplace_back(FILTER_ERROR_FILTER_NAME_NOT_FOUND, -1, -1, token, "");
+ }
+ }
+ }
+ if (pos == String::npos)
+ break; // No more separators found
+ ext = ext.substr(pos + 1); // Remove extension + separator
}
- if (filePatterns.empty())
+ if (filePatterns.empty() && (!pRegexOrExpressionFilter || (pRegexOrExpressionFilter->filefilters.empty() && pRegexOrExpressionFilter->fileExpressionFilters.empty())))
strFileParsed = _T(".*"); // Match everything
else
strFileParsed = strutils::join(filePatterns.begin(), filePatterns.end(), _T("|"));
- if (dirPatterns.empty())
+ if (dirPatterns.empty() && (!pRegexOrExpressionFilter || (pRegexOrExpressionFilter->dirfilters.empty() && pRegexOrExpressionFilter->dirExpressionFilters.empty())))
strDirParsed = _T(".*"); // Match everything
else
strDirParsed = strutils::join(dirPatterns.begin(), dirPatterns.end(), _T("|"));
String strFileParsedExclude = strutils::join(filePatternsExclude.begin(), filePatternsExclude.end(), _T("|"));
String strDirParsedExclude = strutils::join(dirPatternsExclude.begin(), dirPatternsExclude.end(), _T("|"));
- return { strFileParsed, strFileParsedExclude, strDirParsed, strDirParsedExclude };
+ return { strFileParsed, strFileParsedExclude, strDirParsed, strDirParsedExclude, pRegexOrExpressionFilter, pRegexOrExpressionFilterExclude };
}
/**
@@ -403,55 +605,7 @@ std::tuple FileFilterHelper::ParseExtensions(con
*/
String FileFilterHelper::GetFilterNameOrMask() const
{
- String sFilter;
-
- if (!IsUsingMask())
- sFilter = GetFileFilterName(m_sFileFilterPath);
- else
- sFilter = m_sMask;
-
- return sFilter;
-}
-
-/**
- * @brief Set filter.
- *
- * Simple-to-use function to select filter. This function determines
- * filter type so caller doesn't need to care about it.
- *
- * @param [in] filter File mask or filter name.
- * @return true if given filter was set, false if default filter was set.
- * @note If function returns false, you should ask filter set with
- * GetFilterNameOrMask().
- */
-bool FileFilterHelper::SetFilter(const String &filter)
-{
- // If filter is empty string set default filter
- if (filter.empty())
- {
- UseMask(true);
- SetMask(_T("*.*"));
- SetFileFilterPath(_T(""));
- return false;
- }
-
- // Remove leading and trailing whitespace characters from the string.
- String flt = strutils::trim_ws(filter);
-
- String path = GetFileFilterPath(flt);
- if (!path.empty())
- {
- UseMask(false);
- SetFileFilterPath(path);
- }
- else
- {
- UseMask(true);
- SetMask(flt);
- SetFileFilterPath(_T(""));
- return false;
- }
- return true;
+ return m_sMask;
}
/**
@@ -464,9 +618,8 @@ bool FileFilterHelper::SetFilter(const String &filter)
void FileFilterHelper::ReloadUpdatedFilters()
{
DirItem fileInfo;
- String selected;
- vector filters = GetFileFilters(selected);
- vector::const_iterator iter = filters.begin();
+ std::vector filters = GetFileFilters();
+ std::vector::const_iterator iter = filters.begin();
while (iter != filters.end())
{
String path = (*iter).fullpath;
@@ -476,14 +629,7 @@ void FileFilterHelper::ReloadUpdatedFilters()
fileInfo.size != (*iter).fileinfo.size)
{
// Reload filter after changing it
- int retval = m_fileFilterMgr->ReloadFilterFromDisk(path);
-
- if (retval == FILTER_OK)
- {
- // If it was active filter we have to re-set it
- if (path == selected)
- SetFileFilterPath(path);
- }
+ m_fileFilterMgr->ReloadFilterFromDisk(path);
}
++iter;
}
@@ -542,6 +688,13 @@ void FileFilterHelper::CloneFrom(const FileFilterHelper* pHelper)
m_pMaskFileFilter->CloneFrom(pHelper->m_pMaskFileFilter.get());
}
+ if (pHelper->m_pMaskFileFilterExclude)
+ {
+ auto filterList = std::make_unique(FilterList());
+ m_pMaskFileFilterExclude = std::move(filterList);
+ m_pMaskFileFilterExclude->CloneFrom(pHelper->m_pMaskFileFilterExclude.get());
+ }
+
if (pHelper->m_pMaskDirFilter)
{
auto filterList = std::make_unique(FilterList());
@@ -549,6 +702,25 @@ void FileFilterHelper::CloneFrom(const FileFilterHelper* pHelper)
m_pMaskDirFilter->CloneFrom(pHelper->m_pMaskDirFilter.get());
}
+ if (pHelper->m_pMaskDirFilterExclude)
+ {
+ auto filterList = std::make_unique(FilterList());
+ m_pMaskDirFilterExclude = std::move(filterList);
+ m_pMaskDirFilterExclude->CloneFrom(pHelper->m_pMaskDirFilterExclude.get());
+ }
+
+ if (pHelper->m_pRegexOrExpressionFilter)
+ {
+ m_pRegexOrExpressionFilter.reset(new FileFilter());
+ m_pRegexOrExpressionFilter->CloneFrom(pHelper->m_pRegexOrExpressionFilter.get());
+ }
+
+ if (pHelper->m_pRegexOrExpressionFilterExclude)
+ {
+ m_pRegexOrExpressionFilterExclude.reset(new FileFilter());
+ m_pRegexOrExpressionFilterExclude->CloneFrom(pHelper->m_pRegexOrExpressionFilterExclude.get());
+ }
+
if (pHelper->m_fileFilterMgr)
{
auto fileFilterMgr = std::make_unique(FileFilterMgr());
@@ -556,21 +728,8 @@ void FileFilterHelper::CloneFrom(const FileFilterHelper* pHelper)
m_fileFilterMgr->CloneFrom(pHelper->m_fileFilterMgr.get());
}
- m_currentFilter = nullptr;
- if (pHelper->m_currentFilter && pHelper->m_fileFilterMgr)
- {
- int count = pHelper->m_fileFilterMgr->GetFilterCount();
- for (int i = 0; i < count; i++)
- if (pHelper->m_fileFilterMgr->GetFilterByIndex(i) == pHelper->m_currentFilter)
- {
- m_currentFilter = m_fileFilterMgr->GetFilterByIndex(i);
- break;
- }
- }
-
m_sFileFilterPath = pHelper->m_sFileFilterPath;
m_sMask = pHelper->m_sMask;
- m_bUseMask = pHelper->m_bUseMask;
m_sGlobalFilterPath = pHelper->m_sGlobalFilterPath;
m_sUserSelFilterPath = pHelper->m_sUserSelFilterPath;
}
diff --git a/Src/FileFilterHelper.h b/Src/FileFilterHelper.h
index a9aaa0037b2..4090fd27c05 100644
--- a/Src/FileFilterHelper.h
+++ b/Src/FileFilterHelper.h
@@ -14,6 +14,9 @@
class FileFilterMgr;
class FilterList;
struct FileFilter;
+class DIFFITEM;
+class CDiffContext;
+struct FileFilterErrorInfo;
/**
* @brief File extension of file filter files.
@@ -40,8 +43,12 @@ struct FileFilterInfo
class IDiffFilter
{
public:
+ virtual void SetDiffContext(const CDiffContext* pCtxt) = 0;
+ virtual std::vector GetErrorList() const = 0;
virtual bool includeFile(const String& szFileName) const = 0;
+ virtual bool includeFile(const DIFFITEM& di) const = 0;
virtual bool includeDir(const String& szDirName) const = 0;
+ virtual bool includeDir(const DIFFITEM& di) const = 0;
bool includeFile(const String& szFileName1, const String& szFileName2) const
{
if (!szFileName1.empty())
@@ -109,41 +116,47 @@ class FileFilterHelper : public IDiffFilter
String GetGlobalFilterPathWithCreate() const;
String GetUserFilterPathWithCreate() const;
- FileFilterMgr * GetManager() const;
- void SetFileFilterPath(const String& szFileFilterPath);
- std::vector GetFileFilters(String & selected) const;
+ FileFilterMgr* GetManager() const;
+ std::vector GetFileFilters() const;
String GetFileFilterName(const String& filterPath) const;
String GetFileFilterPath(const String& filterName) const;
- void SetUserFilterPath(const String & filterPath);
+ void SetUserFilterPath(const String& filterPath);
+ FileFilter* GetRegexOrExpressionFilter() const { return m_pRegexOrExpressionFilter.get(); }
+ FileFilter* GetRegexOrExpressionFilterExclude() const { return m_pRegexOrExpressionFilterExclude.get(); }
void ReloadUpdatedFilters();
void LoadAllFileFilters();
void LoadFileFilterDirPattern(const String& dir, const String& szPattern);
- void UseMask(bool bUseMask);
void SetMask(const String& strMask);
- bool IsUsingMask() const;
String GetFilterNameOrMask() const;
- bool SetFilter(const String &filter);
+ void SetDiffContext(const CDiffContext* pCtxt) override;
+ std::vector GetErrorList() const override;
bool includeFile(const String& szFileName) const override;
+ bool includeFile(const DIFFITEM& di) const override;
bool includeDir(const String& szDirName) const override;
+ bool includeDir(const DIFFITEM& di) const override;
void CloneFrom(const FileFilterHelper* pHelper);
+
protected:
- std::tuple ParseExtensions(const String &extensions) const;
+ std::tuple, std::shared_ptr>
+ ParseExtensions(const String &extensions) const;
private:
std::unique_ptr m_pMaskFileFilter; /*< Filter for filemasks (*.cpp) */
+ std::unique_ptr m_pMaskFileFilterExclude; /*< Filter for filemasks (*.cpp) */
std::unique_ptr m_pMaskDirFilter; /*< Filter for dirmasks */
- FileFilter * m_currentFilter; /*< Currently selected filefilter */
+ std::unique_ptr m_pMaskDirFilterExclude; /*< Filter for dirmasks */
+ std::shared_ptr m_pRegexOrExpressionFilter;
+ std::shared_ptr m_pRegexOrExpressionFilterExclude;
std::unique_ptr m_fileFilterMgr; /*< Associated FileFilterMgr */
- String m_sFileFilterPath; /*< Path to current filter */
+ std::vector m_sFileFilterPath; /*< Path to current filter */
String m_sMask; /*< File mask (if defined) "*.cpp *.h" etc */
- bool m_bUseMask; /*< If `true` file mask is used, filter otherwise */
String m_sGlobalFilterPath; /*< Path for shared filters */
String m_sUserSelFilterPath; /*< Path for user's private filters */
};
@@ -155,12 +168,3 @@ inline FileFilterMgr * FileFilterHelper::GetManager() const
{
return m_fileFilterMgr.get();
}
-
-/**
- * @brief Returns true if active filter is a mask.
- */
-inline bool FileFilterHelper::IsUsingMask() const
-{
- return m_bUseMask;
-}
-
diff --git a/Src/FileFilterMgr.cpp b/Src/FileFilterMgr.cpp
index 959e1cc2a0d..c90835484e1 100644
--- a/Src/FileFilterMgr.cpp
+++ b/Src/FileFilterMgr.cpp
@@ -7,11 +7,13 @@
#include "pch.h"
#include "FileFilterMgr.h"
+#include "FilterEngine/FilterExpression.h"
#include
#include
#include
+#include
#include "DirTravel.h"
-#include "DirItem.h"
+#include "DiffItem.h"
#include "UnicodeString.h"
#include "FileFilter.h"
#include "UniFile.h"
@@ -21,8 +23,6 @@ using std::vector;
using Poco::Glob;
using Poco::RegularExpression;
-static void AddFilterPattern(vector *filterList, String & str, bool fileFilter);
-
/**
* @brief Destructor, frees all filters.
*/
@@ -111,13 +111,7 @@ void FileFilterMgr::DeleteAllFilters()
m_filters.clear();
}
-/**
- * @brief Add a single pattern (if nonempty & valid) to a pattern list.
- *
- * @param [in] filterList List where pattern is added.
- * @param [in] str Temporary variable (ie, it may be altered)
- */
-static void AddFilterPattern(vector *filterList, String & str, bool fileFilter)
+static bool RemoveComment(String& str)
{
const String& commentLeader = _T("##"); // Starts comment
str = strutils::trim_ws_begin(str);
@@ -125,7 +119,7 @@ static void AddFilterPattern(vector *filterList, String &
// Ignore lines beginning with '##'
size_t pos = str.find(commentLeader);
if (pos == 0)
- return;
+ return true;
// Find possible comment-separator '##'
while (pos != std::string::npos && !(str[pos - 1] == ' ' || str[pos - 1] == '\t'))
@@ -135,20 +129,37 @@ static void AddFilterPattern(vector *filterList, String &
if (pos != std::string::npos)
str = str.substr(0, pos);
str = strutils::trim_ws_end(str);
- if (str.empty())
+ return (str.empty());
+}
+
+/**
+ * @brief Add a single pattern (if nonempty & valid) to a pattern list.
+ *
+ * @param [in] pfilter Pointer to file filter, used for error reporting.
+ * @param [in] filterList List where pattern is added.
+ * @param [in] str Temporary variable (ie, it may be altered)
+ * @param [in] lineNumber Line number in filter file, used for error reporting.
+ */
+static void AddFilterPattern(FileFilter* pfilter, vector *filterList, String & str, bool fileFilter, int lineNumber)
+{
+ if (RemoveComment(str))
return;
+ pfilter->AddFilterPattern(filterList, str, fileFilter, lineNumber);
+}
- int re_opts = RegularExpression::RE_CASELESS;
- std::string regexString = ucr::toUTF8(str);
- re_opts |= RegularExpression::RE_UTF8;
- try
- {
- filterList->push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts, fileFilter)));
- }
- catch (...)
- {
- // TODO:
- }
+/**
+ * @brief Add a single expression (if nonempty & valid) to a expression list.
+ *
+ * @param [in] pfilter Pointer to file filter, used for error reporting.
+ * @param [in] filterList List where expression is added.
+ * @param [in] str Temporary variable (ie, it may be altered)
+ * @param [in] lineNumber Line number in filter file, used for error reporting.
+*/
+static void AddFilterExpression(FileFilter* pfilter, vector* filterList, String& str, int lineNumber)
+{
+ if (RemoveComment(str))
+ return;
+ pfilter->AddFilterExpression(filterList, str, lineNumber);
}
/**
@@ -181,6 +192,7 @@ FileFilter * FileFilterMgr::LoadFilterFile(const String& szFilepath, int & error
String sLine;
bool lossy = false;
bool bLinesLeft = true;
+ int lineNumber = 0;
do
{
// Returns false when last line is read
@@ -219,26 +231,51 @@ FileFilter * FileFilterMgr::LoadFilterFile(const String& szFilepath, int & error
{
// file filter
String str = sLine.substr(2);
- AddFilterPattern(&pfilter->filefilters, str, true);
+ AddFilterPattern(pfilter, &pfilter->filefilters, str, true, lineNumber);
}
else if (0 == sLine.compare(0, 2, _T("d:"), 2))
{
// directory filter
String str = sLine.substr(2);
- AddFilterPattern(&pfilter->dirfilters, str, false);
+ AddFilterPattern(pfilter, &pfilter->dirfilters, str, false, lineNumber);
+ }
+ else if (0 == sLine.compare(0, 3, _T("fe:"), 3))
+ {
+ // file expression filter
+ String str = sLine.substr(3);
+ AddFilterExpression(pfilter, &pfilter->fileExpressionFilters, str, lineNumber);
+ }
+ else if (0 == sLine.compare(0, 3, _T("de:"), 3))
+ {
+ // directory expression filter
+ String str = sLine.substr(3);
+ AddFilterExpression(pfilter, &pfilter->dirExpressionFilters, str, lineNumber);
}
else if (0 == sLine.compare(0, 3, _T("f!:"), 3))
{
// file filter
String str = sLine.substr(3);
- AddFilterPattern(&pfilter->filefiltersExclude, str, true);
+ AddFilterPattern(pfilter, &pfilter->filefiltersExclude, str, true, lineNumber);
}
else if (0 == sLine.compare(0, 3, _T("d!:"), 3))
{
// directory filter
String str = sLine.substr(3);
- AddFilterPattern(&pfilter->dirfiltersExclude, str, false);
+ AddFilterPattern(pfilter, &pfilter->dirfiltersExclude, str, false, lineNumber);
+ }
+ else if (0 == sLine.compare(0, 4, _T("fe!:"), 4))
+ {
+ // file expression filter
+ String str = sLine.substr(4);
+ AddFilterExpression(pfilter, &pfilter->fileExpressionFiltersExclude, str, lineNumber);
}
+ else if (0 == sLine.compare(0, 4, _T("de!:"), 4))
+ {
+ // directory expression filter
+ String str = sLine.substr(4);
+ AddFilterExpression(pfilter, &pfilter->dirExpressionFiltersExclude, str, lineNumber);
+ }
+ lineNumber++;
} while (bLinesLeft);
return pfilter;
@@ -277,90 +314,6 @@ FileFilter * FileFilterMgr::GetFilterByIndex(int i)
return m_filters[i].get();
}
-/**
- * @brief Test given string against given regexp list.
- *
- * @param [in] filterList List of regexps to test against.
- * @param [in] szTest String to test against regexps.
- * @return true if string passes
- * @note Matching stops when first match is found.
- */
-bool TestAgainstRegList(const vector *filterList, const String& szTest)
-{
- if (filterList->size() == 0)
- return false;
-
- std::string compString, compStringFileName;
- ucr::toUTF8(szTest, compString);
- vector::const_iterator iter = filterList->begin();
- while (iter != filterList->end())
- {
- RegularExpression::Match match;
- try
- {
- if ((*iter)->_fileNameOnly && compStringFileName.empty())
- ucr::toUTF8(paths::FindFileName(szTest), compStringFileName);
- if ((*iter)->regexp.match((*iter)->_fileNameOnly ? compStringFileName : compString, 0, match) > 0)
- return true;
- }
- catch (...)
- {
- // TODO:
- }
-
- ++iter;
- }
- return false;
-}
-
-/**
- * @brief Test given filename against filefilter.
- *
- * Test filename against active filefilter. If matching rule is found
- * we must first determine type of rule that matched. If we return false
- * from this function directory scan marks file as skipped.
- *
- * @param [in] pFilter Pointer to filefilter
- * @param [in] szFileName Filename to test
- * @return true if file passes the filter
- */
-bool FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
- const String& szFileName) const
-{
- if (pFilter == nullptr)
- return true;
- if (TestAgainstRegList(&pFilter->filefilters, szFileName))
- {
- if (pFilter->filefiltersExclude.empty() || !TestAgainstRegList(&pFilter->filefiltersExclude, szFileName))
- return !pFilter->default_include;
- }
- return pFilter->default_include;
-}
-
-/**
- * @brief Test given directory name against filefilter.
- *
- * Test directory name against active filefilter. If matching rule is found
- * we must first determine type of rule that matched. If we return false
- * from this function directory scan marks file as skipped.
- *
- * @param [in] pFilter Pointer to filefilter
- * @param [in] szDirName Directory name to test
- * @return true if directory name passes the filter
- */
-bool FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
- const String& szDirName) const
-{
- if (pFilter == nullptr)
- return true;
- if (TestAgainstRegList(&pFilter->dirfilters, szDirName))
- {
- if (pFilter->dirfiltersExclude.empty() || !TestAgainstRegList(&pFilter->dirfiltersExclude, szDirName))
- return !pFilter->default_include;
- }
- return pFilter->default_include;
-}
-
/**
* @brief Reload filter from disk
*
diff --git a/Src/FileFilterMgr.h b/Src/FileFilterMgr.h
index 88d1b358e60..acad068ab20 100644
--- a/Src/FileFilterMgr.h
+++ b/Src/FileFilterMgr.h
@@ -57,10 +57,6 @@ class FileFilterMgr
FileFilter * GetFilterByIndex(int i);
String GetFullpath(FileFilter * pfilter) const;
- // methods to actually use filter
- bool TestFileNameAgainstFilter(const FileFilter * pFilter, const String& szFileName) const;
- bool TestDirNameAgainstFilter(const FileFilter * pFilter, const String& szDirName) const;
-
void DeleteAllFilters();
void CloneFrom(const FileFilterMgr* fileFilterMgr);
diff --git a/Src/FileFiltersDlg.cpp b/Src/FileFiltersDlg.cpp
index d5b7b6cebf2..4ac52e1ac27 100644
--- a/Src/FileFiltersDlg.cpp
+++ b/Src/FileFiltersDlg.cpp
@@ -37,7 +37,10 @@ IMPLEMENT_DYNCREATE(FileFiltersDlg, CTrPropertyPage)
/**
* @brief Constructor.
*/
-FileFiltersDlg::FileFiltersDlg() : CTrPropertyPage(FileFiltersDlg::IDD)
+FileFiltersDlg::FileFiltersDlg()
+ : CTrPropertyPage(FileFiltersDlg::IDD)
+ , m_pFileFilterHelper(new FileFilterHelper())
+ , m_pFileFilterHelperOrg(nullptr)
{
m_strCaption = theApp.LoadDialogCaption(m_lpszTemplateName).c_str();
m_psp.pszTitle = m_strCaption;
@@ -50,13 +53,16 @@ void FileFiltersDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(FileFiltersDlg)
+ DDX_CBStringExact(pDX, IDC_FILTERFILE_MASK, m_sMask);
DDX_Control(pDX, IDC_FILTERFILE_LIST, m_listFilters);
+ DDX_Control(pDX, IDC_FILTERFILE_MASK, m_ctlMask);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(FileFiltersDlg, CTrPropertyPage)
//{{AFX_MSG_MAP(FileFiltersDlg)
+ ON_CBN_KILLFOCUS(IDC_FILTERFILE_MASK, OnKillFocusFilterfileMask)
ON_BN_CLICKED(IDC_FILTERFILE_EDITBTN, OnFiltersEditbtn)
ON_NOTIFY(NM_DBLCLK, IDC_FILTERFILE_LIST, OnDblclkFiltersList)
ON_WM_MOUSEMOVE()
@@ -64,42 +70,20 @@ BEGIN_MESSAGE_MAP(FileFiltersDlg, CTrPropertyPage)
ON_BN_CLICKED(IDC_FILTERFILE_NEWBTN, OnBnClickedFilterfileNewbutton)
ON_BN_CLICKED(IDC_FILTERFILE_DELETEBTN, OnBnClickedFilterfileDelete)
ON_COMMAND(ID_HELP, OnHelp)
- //}}AFX_MSG_MAP
ON_NOTIFY(LVN_ITEMCHANGED, IDC_FILTERFILE_LIST, OnLvnItemchangedFilterfileList)
ON_NOTIFY(LVN_GETINFOTIP, IDC_FILTERFILE_LIST, OnInfoTip)
ON_BN_CLICKED(IDC_FILTERFILE_INSTALL, OnBnClickedFilterfileInstall)
+ //}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CFiltersDlg message handlers
-/**
- * @brief Set array of filters.
- * @param [in] fileFilters Array of filters to show in the dialog.
- * @note Call this before actually showing the dialog.
- */
-void FileFiltersDlg::SetFilterArray(const vector& fileFilters)
-{
- m_Filters = fileFilters;
-}
-
-/**
- * @brief Returns path (cont. filename) of selected filter
- * @return Full path to selected filter file.
- */
-String FileFiltersDlg::GetSelected()
+void FileFiltersDlg::SetFileFilterHelper(FileFilterHelper* pFileFilterHelper)
{
- return m_sFileFilterPath;
-}
-
-/**
- * @brief Set path of selected filter.
- * @param [in] Path for selected filter.
- * @note Call this before actually showing the dialog.
- */
-void FileFiltersDlg::SetSelected(const String & selected)
-{
- m_sFileFilterPath = selected;
+ m_pFileFilterHelper->CloneFrom(pFileFilterHelper);
+ m_pFileFilterHelperOrg = pFileFilterHelper;
+ m_Filters = m_pFileFilterHelper->GetFileFilters();
}
/**
@@ -109,7 +93,7 @@ void FileFiltersDlg::InitList()
{
// Show selection across entire row.
// Also enable infotips.
- m_listFilters.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
+ m_listFilters.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
const int lpx = CClientDC(this).GetDeviceCaps(LOGPIXELSX);
auto pointToPixel = [lpx](int point) { return MulDiv(point, lpx, 72); };
@@ -121,17 +105,9 @@ void FileFiltersDlg::InitList()
title = _("Location");
m_listFilters.InsertColumn(2, title.c_str(), LVCFMT_LEFT, pointToPixel(262));
- title = _("");
- m_listFilters.InsertItem(1, title.c_str());
- m_listFilters.SetItemText(0, 1, title.c_str());
- m_listFilters.SetItemText(0, 2, title.c_str());
-
- const int count = (int) m_Filters.size();
-
+ const int count = (int) m_pFileFilterHelper->GetFileFilters().size();
for (int i = 0; i < count; i++)
- {
AddToGrid(i);
- }
}
/**
@@ -140,7 +116,7 @@ void FileFiltersDlg::InitList()
*/
void FileFiltersDlg::SelectFilterByIndex(int index)
{
- m_listFilters.SetItemState(index, LVIS_SELECTED, LVIS_SELECTED);
+ m_listFilters.SetCheck(index, TRUE);
bool bPartialOk = false;
m_listFilters.EnsureVisible(index, bPartialOk);
}
@@ -161,6 +137,36 @@ void FileFiltersDlg::SelectFilterByFilePath(const String& path)
}
}
+/**
+ * @brief Remove preset filters from filter expression and return the rest.
+ */
+static String RemovePresetFilters(const String& filterExpression, std::vector& presetFilters)
+{
+ auto parts = strutils::split(filterExpression, ';');
+ std::vector result;
+ for (const auto& part : parts)
+ {
+ const String partTrimmed = strutils::trim_ws(String(part.data(), part.length()));
+ if (partTrimmed.substr(0, 3) == _T("fp:"))
+ presetFilters.push_back(partTrimmed.substr(3));
+ else
+ result.push_back(partTrimmed);
+ }
+ return strutils::join(result.begin(), result.end(), _T(";"));
+}
+
+static void SetCheckedState(CListCtrl& list, std::vector& presetFilters)
+{
+ const int count = list.GetItemCount();
+ for (int i = 0; i < count; i++)
+ {
+ String desc = list.GetItemText(i, 0);
+ const bool isChecked = std::find(presetFilters.begin(), presetFilters.end(), desc) != presetFilters.end();
+ if (isChecked)
+ list.SetCheck(i, true);
+ }
+}
+
/**
* @brief Called before dialog is shown.
* @return Always TRUE.
@@ -169,23 +175,16 @@ BOOL FileFiltersDlg::OnInitDialog()
{
CTrPropertyPage::OnInitDialog();
+ m_ctlMask.SetFileControlStates(true);
+ m_ctlMask.LoadState(_T("Files\\Ext"));
+
InitList();
- if (m_sFileFilterPath.empty())
- {
- SelectFilterByIndex(0);
- return TRUE;
- }
+ std::vector presetFilters;
+ String filterExpression = RemovePresetFilters(m_pFileFilterHelper->GetFilterNameOrMask(), presetFilters);
+ SetDlgItemText(IDC_FILTERFILE_MASK, filterExpression.c_str());
- int count = m_listFilters.GetItemCount();
- for (int i = 0; i < count; i++)
- {
- String desc = m_listFilters.GetItemText(i, 2);
- if (strutils::compare_nocase(desc, m_sFileFilterPath) == 0)
- {
- SelectFilterByIndex(i);
- }
- }
+ SetCheckedState(m_listFilters, presetFilters);
SetButtonState();
@@ -200,7 +199,7 @@ BOOL FileFiltersDlg::OnInitDialog()
void FileFiltersDlg::AddToGrid(int filterIndex)
{
const FileFilterInfo & filterinfo = m_Filters.at(filterIndex);
- const int item = filterIndex + 1;
+ const int item = filterIndex;
m_listFilters.InsertItem(item, filterinfo.name.c_str());
m_listFilters.SetItemText(item, 1, filterinfo.description.c_str());
@@ -212,14 +211,43 @@ void FileFiltersDlg::AddToGrid(int filterIndex)
*/
void FileFiltersDlg::OnOK()
{
- int sel = m_listFilters.GetNextItem(-1, LVNI_SELECTED);
- m_sFileFilterPath = m_listFilters.GetItemText(sel, 2);
+ String mask = m_sMask;
+ const int count = m_listFilters.GetItemCount();
+ for (int i = 0; i < count; i++)
+ {
+ const bool checked = m_listFilters.GetCheck(i);
+ if (checked)
+ {
+ if (!mask.empty())
+ mask += _T(";");
+ mask += _T("fp:") + m_listFilters.GetItemText(i, 0);
+ }
+ }
+
+ m_pFileFilterHelper->SetMask(mask);
+ m_pFileFilterHelperOrg->CloneFrom(m_pFileFilterHelper.get());
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("FilterStartPage"), GetParentSheet()->GetActiveIndex());
+ m_ctlMask.SetWindowTextW(mask.c_str());
+ m_ctlMask.SaveState(_T("Files\\Ext"));
+
CDialog::OnOK();
}
+void FileFiltersDlg::OnKillFocusFilterfileMask()
+{
+ String filterExpressionOld;
+ GetDlgItemText(IDC_FILTERFILE_MASK, filterExpressionOld);
+ std::vector presetFilters;
+ String filterExpression = RemovePresetFilters(filterExpressionOld, presetFilters);
+ if (filterExpression != filterExpressionOld)
+ {
+ SetDlgItemText(IDC_FILTERFILE_MASK, filterExpression.c_str());
+ SetCheckedState(m_listFilters, presetFilters);
+ }
+}
+
/**
* @brief Open selected filter for editing.
*
@@ -234,16 +262,10 @@ void FileFiltersDlg::OnOK()
*/
void FileFiltersDlg::OnFiltersEditbtn()
{
- int sel =- 1;
-
+ int sel = -1;
sel = m_listFilters.GetNextItem(sel, LVNI_SELECTED);
-
- // Can't edit first "None"
- if (sel > 0)
- {
- String path = m_listFilters.GetItemText(sel, 2);
- EditFileFilter(path);
- }
+ String path = m_listFilters.GetItemText(sel, 2);
+ EditFileFilter(path);
}
/**
@@ -268,23 +290,10 @@ void FileFiltersDlg::OnDblclkFiltersList(NMHDR* pNMHDR, LRESULT* pResult)
*pResult = 0;
}
-/**
- * @brief Is item in list the item?
- * @param [in] item Item to test.
- * @return true if item is item.
- */
-bool FileFiltersDlg::IsFilterItemNone(int item) const
-{
- String txtNone = _("");
- String txt = m_listFilters.GetItemText(item, 0);
-
- return (strutils::compare_nocase(txt, txtNone) == 0);
-}
-
/**
* @brief Called when item state is changed.
*
- * Disable the "Test", "Edit" and "Remove" buttons when no item is selected or "None" filter is selected.
+ * Disable the "Test", "Edit" and "Remove" buttons when no item is selected.
* @param [in] pNMHDR Listview item data.
* @param [out] pResult Result of the action is returned in here.
*/
@@ -351,24 +360,10 @@ void FileFiltersDlg::OnBnClickedFilterfileTestButton()
{
UpdateData(TRUE);
- int sel = m_listFilters.GetNextItem(-1, LVNI_SELECTED);
- if (sel == -1)
- return;
- if (IsFilterItemNone(sel))
- return;
-
- m_sFileFilterPath = m_listFilters.GetItemText(sel, 2);
-
// Ensure filter is up-to-date (user probably just edited it)
- auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
- pGlobalFileFilter->ReloadUpdatedFilters();
+ m_pFileFilterHelper->ReloadUpdatedFilters();
- FileFilterMgr *pMgr = pGlobalFileFilter->GetManager();
- FileFilter * pFileFilter = pMgr->GetFilterByPath(m_sFileFilterPath);
- if (pFileFilter == nullptr)
- return;
-
- CTestFilterDlg dlg(this, pFileFilter, pMgr);
+ CTestFilterDlg dlg(this, m_pFileFilterHelper.get());
dlg.DoModal();
}
@@ -470,9 +465,8 @@ void FileFiltersDlg::OnBnClickedFilterfileNewbutton()
if (retval == FILTER_OK)
{
// Remove all from filterslist and re-add so we can update UI
- String selected;
pGlobalFileFilter->LoadAllFileFilters();
- m_Filters = pGlobalFileFilter->GetFileFilters(selected);
+ m_Filters = pGlobalFileFilter->GetFileFilters();
UpdateFiltersList();
SelectFilterByFilePath(s);
@@ -485,39 +479,31 @@ void FileFiltersDlg::OnBnClickedFilterfileNewbutton()
*/
void FileFiltersDlg::OnBnClickedFilterfileDelete()
{
- String path;
- int sel =- 1;
-
+ int sel = -1;
sel = m_listFilters.GetNextItem(sel, LVNI_SELECTED);
+ const String path = m_listFilters.GetItemText(sel, 2);
- // Can't delete first "None"
- if (sel > 0)
+ String sConfirm = strutils::format_string1(_("Are you sure you want to delete\n\n%1 ?"), path);
+ int res = AfxMessageBox(sConfirm.c_str(), MB_ICONWARNING | MB_YESNO);
+ if (res == IDYES)
{
- path = m_listFilters.GetItemText(sel, 2);
+ if (DeleteFile(path.c_str()))
+ {
+ auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
+ FileFilterMgr *pMgr = pGlobalFileFilter->GetManager();
+ pMgr->RemoveFilter(path);
+
+ // Remove all from filterslist and re-add so we can update UI
+ m_Filters = pGlobalFileFilter->GetFileFilters();
- String sConfirm = strutils::format_string1(_("Are you sure you want to delete\n\n%1 ?"), path);
- int res = AfxMessageBox(sConfirm.c_str(), MB_ICONWARNING | MB_YESNO);
- if (res == IDYES)
+ UpdateFiltersList();
+ }
+ else
{
- if (DeleteFile(path.c_str()))
- {
- auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
- FileFilterMgr *pMgr = pGlobalFileFilter->GetManager();
- pMgr->RemoveFilter(path);
-
- // Remove all from filterslist and re-add so we can update UI
- String selected;
- m_Filters = pGlobalFileFilter->GetFileFilters(selected);
-
- UpdateFiltersList();
- }
- else
- {
- String msg = strutils::format_string1(
- _("Failed to delete filter:\n%1\n\nFile may be read-only."),
- path);
- AfxMessageBox(msg.c_str(), MB_ICONSTOP);
- }
+ String msg = strutils::format_string1(
+ _("Failed to delete filter:\n%1\n\nFile may be read-only."),
+ path);
+ AfxMessageBox(msg.c_str(), MB_ICONSTOP);
}
}
SetButtonState();
@@ -528,15 +514,9 @@ void FileFiltersDlg::OnBnClickedFilterfileDelete()
*/
void FileFiltersDlg::UpdateFiltersList()
{
- int count = (int) m_Filters.size();
-
m_listFilters.DeleteAllItems();
- String title = _("");
- m_listFilters.InsertItem(1, title.c_str());
- m_listFilters.SetItemText(0, 1, title.c_str());
- m_listFilters.SetItemText(0, 2, title.c_str());
-
+ const int count = (int) m_Filters.size();
for (int i = 0; i < count; i++)
{
AddToGrid(i);
@@ -597,8 +577,7 @@ void FileFiltersDlg::OnBnClickedFilterfileInstall()
pMgr->AddFilter(userPath);
// Remove all from filterslist and re-add so we can update UI
- String selected;
- m_Filters = pGlobalFileFilter->GetFileFilters(selected);
+ m_Filters = pGlobalFileFilter->GetFileFilters();
UpdateFiltersList();
SelectFilterByFilePath(userPath);
@@ -607,20 +586,13 @@ void FileFiltersDlg::OnBnClickedFilterfileInstall()
}
/**
- * @brief Disable the "Test", "Edit" and "Remove" buttons when no item is selected or "None" filter is selected.
+ * @brief Disable the "Test", "Edit" and "Remove" buttons when no item is selected.
*/
void FileFiltersDlg::SetButtonState()
{
- bool isNone = true;
-
int sel = -1;
sel = m_listFilters.GetNextItem(sel, LVNI_SELECTED);
- if (sel != -1)
- {
- String txtNone = _("");
- String txt = m_listFilters.GetItemText(sel, 0);
- isNone = strutils::compare_nocase(txt, txtNone) == 0;
- }
+ const bool isNone = (sel == -1);
EnableDlgItem(IDC_FILTERFILE_TEST_BTN, !isNone);
EnableDlgItem(IDC_FILTERFILE_EDITBTN, !isNone);
diff --git a/Src/FileFiltersDlg.h b/Src/FileFiltersDlg.h
index 47393c0b3b9..61010f718c8 100644
--- a/Src/FileFiltersDlg.h
+++ b/Src/FileFiltersDlg.h
@@ -8,8 +8,8 @@
#include "TrDialogs.h"
#include
-
-struct FileFilterInfo;
+#include "FileFilterHelper.h"
+#include "SuperComboBox.h"
/**
* @brief Class for dialog allowing user to select
@@ -22,15 +22,16 @@ class FileFiltersDlg : public CTrPropertyPage
// Construction
public:
FileFiltersDlg(); // standard constructor
- void SetFilterArray(const std::vector& fileFilters);
- String GetSelected();
- void SetSelected(const String & selected);
+ void SetFileFilterHelper(FileFilterHelper* pFileFilterHelper);
// Implementation data
private:
- String m_sFileFilterPath;
CPoint m_ptLastMousePos;
+ String m_sMask;
+ std::unique_ptr m_pFileFilterHelper;
+ FileFilterHelper* m_pFileFilterHelperOrg;
std::vector m_Filters;
+ CSuperComboBox m_ctlMask;
// Dialog Data
//{{AFX_DATA(FileFiltersDlg)
@@ -44,7 +45,6 @@ class FileFiltersDlg : public CTrPropertyPage
void SelectFilterByIndex(int index);
void SelectFilterByFilePath(const String& path);
void AddToGrid(int filterIndex);
- bool IsFilterItemNone(int item) const;
void UpdateFiltersList();
void EditFileFilter(const String& path);
@@ -59,6 +59,7 @@ class FileFiltersDlg : public CTrPropertyPage
//{{AFX_MSG(FileFiltersDlg)
virtual BOOL OnInitDialog() override;
virtual void OnOK();
+ afx_msg void OnKillFocusFilterfileMask();
afx_msg void OnFiltersEditbtn();
afx_msg void OnDblclkFiltersList(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnLvnItemchangedFilterfileList(NMHDR *pNMHDR, LRESULT *pResult);
diff --git a/Src/FileVersion.cpp b/Src/FileVersion.cpp
index 5e49f73edbb..e49609bedce 100644
--- a/Src/FileVersion.cpp
+++ b/Src/FileVersion.cpp
@@ -17,9 +17,20 @@
* @brief Default constructor.
*/
FileVersion::FileVersion()
-: m_fileVersionMS(0xffffffff)
-, m_fileVersionLS(0xffffffff)
{
+ Clear();
+}
+
+FileVersion::FileVersion(const FileVersion& other)
+{
+ m_fileVersion.store(other.m_fileVersion.load(std::memory_order_relaxed), std::memory_order_relaxed);
+}
+
+FileVersion& FileVersion::operator=(const FileVersion& other)
+{
+ if (this != &other)
+ m_fileVersion.store(other.m_fileVersion.load(std::memory_order_relaxed), std::memory_order_relaxed);
+ return *this;
}
/**
@@ -29,11 +40,14 @@ FileVersion::FileVersion()
*/
String FileVersion::GetFileVersionString() const
{
- if (m_fileVersionMS == 0xffffffff && m_fileVersionLS >= 0xfffffffe)
+ auto version = m_fileVersion.load(std::memory_order_relaxed);
+ if (version >= 0xFFFFFFFFFFFFFFFEULL)
return _T("");
- return strutils::format(_T("%u.%u.%u.%u"), HIWORD(m_fileVersionMS),
- LOWORD(m_fileVersionMS), HIWORD(m_fileVersionLS),
- LOWORD(m_fileVersionLS));
+ unsigned int fileVersionMS = static_cast(version >> 32);
+ unsigned int fileVersionLS = static_cast(version & 0xFFFFFFFFULL);
+ return strutils::format(_T("%u.%u.%u.%u"), HIWORD(fileVersionMS),
+ LOWORD(fileVersionMS), HIWORD(fileVersionLS),
+ LOWORD(fileVersionLS));
}
diff --git a/Src/FileVersion.h b/Src/FileVersion.h
index 8c08df0644c..ac5ef136414 100644
--- a/Src/FileVersion.h
+++ b/Src/FileVersion.h
@@ -5,6 +5,7 @@
*/
#pragma once
+#include
#include "UnicodeString.h"
/**
@@ -15,17 +16,18 @@
class FileVersion
{
private:
- unsigned m_fileVersionMS; //*< File version most significant dword. */
- unsigned m_fileVersionLS; //*< File version least significant dword. */
+ std::atomic_uint64_t m_fileVersion;
public:
FileVersion();
+ FileVersion(const FileVersion& other);
+ FileVersion& operator=(const FileVersion& other);
void Clear();
- bool IsCleared() const { return m_fileVersionMS == 0xffffffff && m_fileVersionLS == 0xffffffff; };
+ bool IsCleared() const;
void SetFileVersion(unsigned versionMS, unsigned versionLS);
- void SetFileVersionNone() { m_fileVersionMS = 0xffffffff; m_fileVersionLS = 0xfffffffe; };
+ void SetFileVersionNone();
String GetFileVersionString() const;
- uint64_t GetFileVersionQWORD() const { return (static_cast(m_fileVersionMS) << 32) + m_fileVersionLS; };
+ uint64_t GetFileVersionQWORD() const;
};
/**
@@ -33,7 +35,23 @@ class FileVersion
*/
inline void FileVersion::Clear()
{
- m_fileVersionMS = m_fileVersionLS = 0xffffffff;
+ m_fileVersion.store(0xFFFFFFFFFFFFFFFFULL, std::memory_order_relaxed);
+}
+
+inline bool FileVersion::IsCleared() const
+{
+ return m_fileVersion.load(std::memory_order_relaxed) == 0xFFFFFFFFFFFFFFFFULL;
+}
+
+inline void FileVersion::SetFileVersion(unsigned versionMS, unsigned versionLS)
+{
+ uint64_t combined = (static_cast(versionMS) << 32) | versionLS;
+ m_fileVersion.store(combined, std::memory_order_relaxed);
+}
+
+inline void FileVersion::SetFileVersionNone()
+{
+ m_fileVersion.store(0xFFFFFFFFFFFFFFFEULL, std::memory_order_relaxed);
}
/**
@@ -41,9 +59,7 @@ inline void FileVersion::Clear()
* @param [in] versionMS Most significant dword for version.
* @param [in] versionLS Least significant dword for version.
*/
-inline void FileVersion::SetFileVersion(unsigned versionMS, unsigned versionLS)
+inline uint64_t FileVersion::GetFileVersionQWORD() const
{
- m_fileVersionMS = versionMS;
- m_fileVersionLS = versionLS;
+ return m_fileVersion.load(std::memory_order_relaxed);
}
-
diff --git a/Src/FilterEngine/FileContentRef.cpp b/Src/FilterEngine/FileContentRef.cpp
new file mode 100644
index 00000000000..292c9d92905
--- /dev/null
+++ b/Src/FilterEngine/FileContentRef.cpp
@@ -0,0 +1,190 @@
+#include "pch.h"
+#include "FileContentRef.h"
+#include "UnicodeString.h"
+#include "OptionsMgr.h"
+#include "OptionsDef.h"
+#include "UniFile.h"
+#include "codepage_detect.h"
+#include "paths.h"
+#include "MergeApp.h"
+#include
+#include
+#include
+#include
+#include
+
+static void GuessEncoding(UniMemFile& file, const String& path)
+{
+ file.ReadBom();
+ if (!file.HasBom())
+ {
+ int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
+ int64_t fileSize = file.GetFileSize();
+ FileTextEncoding encoding = codepage_detect::Guess(
+ paths::FindExtension(path), file.GetBase(), static_cast(
+ fileSize < static_cast(codepage_detect::BufSize) ?
+ fileSize : static_cast(codepage_detect::BufSize)),
+ iGuessEncodingType);
+ file.SetCodepage(encoding.m_codepage);
+ }
+}
+
+bool FileContentRef::operator==(const FileContentRef& other) const
+{
+ try {
+ Poco::FileInputStream fs1(ucr::toUTF8(path), std::ios::binary);
+ Poco::FileInputStream fs2(ucr::toUTF8(other.path), std::ios::binary);
+
+ if (!fs1.good() || !fs2.good()) return false;
+
+ const size_t bufferSize = 4096;
+ char buffer1[bufferSize];
+ char buffer2[bufferSize];
+
+ while (true) {
+ fs1.read(buffer1, bufferSize);
+ fs2.read(buffer2, bufferSize);
+
+ std::streamsize count1 = fs1.gcount();
+ std::streamsize count2 = fs2.gcount();
+
+ if (count1 != count2) return false;
+ if (count1 == 0) return true; // end of both
+
+ if (std::memcmp(buffer1, buffer2, static_cast(count1)) != 0)
+ return false;
+ }
+ }
+ catch (const Poco::Exception&)
+ {
+ return false;
+ }
+}
+
+bool FileContentRef::Contains(const std::string& str) const
+{
+ UniMemFile file;
+ if (!file.OpenReadOnly(path))
+ return false;
+ GuessEncoding(file, path);
+ String searchStr = ucr::toTString(str);
+ strutils::makelower(searchStr);
+ std::boyer_moore_horspool_searcher searcher(searchStr.begin(), searchStr.end());
+ bool linesToRead = true;
+ bool found = false;
+ do
+ {
+ bool lossy;
+ String line, eol;
+ linesToRead = file.ReadString(line, eol, &lossy);
+ strutils::makelower(line);
+ using iterator = String::const_iterator;
+ std::pair result = searcher(line.begin(), line.end());
+ if (result.first != result.second)
+ {
+ found = true;
+ break;
+ }
+ } while (linesToRead);
+ file.Close();
+ return found;
+}
+
+bool FileContentRef::REContains(const Poco::RegularExpression& regexp) const
+{
+ UniMemFile file;
+ if (!file.OpenReadOnly(path))
+ return false;
+ GuessEncoding(file, path);
+ bool linesToRead = true;
+ bool found = false;
+ try
+ {
+ do
+ {
+ bool lossy;
+ String line, eol;
+ linesToRead = file.ReadString(line, eol, &lossy);
+ Poco::RegularExpression::Match match;
+ if (regexp.match(ucr::toUTF8(line), match) > 0)
+ {
+ found = true;
+ break;
+ }
+ } while (linesToRead);
+ }
+ catch (const Poco::RegularExpressionException&)
+ {
+ }
+ file.Close();
+ return found;
+}
+
+std::string FileContentRef::Sublines(int start, int len) const
+{
+ UniMemFile file;
+ if (!file.OpenReadOnly(path))
+ return "";
+ GuessEncoding(file, path);
+ bool linesToRead = true;
+ std::vector lines;
+ if (start >= 0 && len >= 0)
+ {
+ size_t count = 0;
+ do
+ {
+ bool lossy;
+ String line, eol;
+ linesToRead = file.ReadString(line, eol, &lossy);
+ if (count >= start && count < start + len && (!line.empty() || !eol.empty()))
+ lines.push_back(line + eol);
+ if (lines.size() >= static_cast(len))
+ break;
+ ++count;
+ } while (linesToRead);
+ file.Close();
+ return ucr::toUTF8(strutils::join(lines.begin(), lines.end(), _T("")));
+ }
+ do
+ {
+ bool lossy;
+ String line, eol;
+ linesToRead = file.ReadString(line, eol, &lossy);
+ if (!line.empty() || !eol.empty())
+ lines.push_back(line + eol);
+ } while (linesToRead);
+ if (start < 0)
+ {
+ start = static_cast(lines.size()) + start;
+ if (start < 0)
+ start = 0;
+ }
+ if (start >= static_cast(lines.size()))
+ return "";
+ if (len < 0)
+ len = static_cast(lines.size()) - start;
+ size_t end = std::min(start + len, lines.size());
+ file.Close();
+ return ucr::toUTF8(strutils::join(lines.begin() + start, lines.begin() + end, _T("")));
+}
+
+size_t FileContentRef::LineCount() const
+{
+ UniMemFile file;
+ if (!file.OpenReadOnly(path))
+ return static_cast(-1);
+ GuessEncoding(file, path);
+ bool linesToRead = true;
+ size_t count = 0;
+ do
+ {
+ bool lossy;
+ String line, eol;
+ linesToRead = file.ReadString(line, eol, &lossy);
+ if (!line.empty() || !eol.empty())
+ ++count;
+ } while (linesToRead);
+ file.Close();
+ return count;
+}
+
diff --git a/Src/FilterEngine/FileContentRef.h b/Src/FilterEngine/FileContentRef.h
new file mode 100644
index 00000000000..3afe7de3b98
--- /dev/null
+++ b/Src/FilterEngine/FileContentRef.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "DiffFileInfo.h"
+#include
+
+namespace Poco { class RegularExpression; }
+
+struct FileContentRef
+{
+ String path;
+ DiffFileInfo item;
+ bool operator==(const FileContentRef& other) const;
+ bool Contains(const std::string& str) const;
+ bool REContains(const Poco::RegularExpression& regexp) const;
+ std::string Sublines(int start, int len) const;
+ size_t LineCount() const;
+};
diff --git a/Src/FilterEngine/FilterEngine.vcxitems b/Src/FilterEngine/FilterEngine.vcxitems
new file mode 100644
index 00000000000..22459c36cf8
--- /dev/null
+++ b/Src/FilterEngine/FilterEngine.vcxitems
@@ -0,0 +1,66 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ {9c37e5d8-1dc0-4eac-aadb-5fc8be4fb1bc}
+
+
+
+ %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)
+
+
+
+
+
+
+
+ pch.h
+ $(IntDir)$(TargetName)2.pch
+
+
+ pch.h
+ $(IntDir)$(TargetName)2.pch
+
+
+ pch.h
+ $(IntDir)$(TargetName)2.pch
+
+
+ pch.h
+ $(IntDir)$(TargetName)2.pch
+
+
+ true
+
+
+ pch.h
+ $(IntDir)$(TargetName)2.pch
+
+
+
+
+
+
+ Document
+ $(SolutionDir)\Build\msys2\usr\bin\re2c.exe %(FullPath) -o %(RelativeDir)\FilterLexer.cpp
+ %(RelativeDir)\FilterLexer.cpp
+
+
+
+
+ false
+ Document
+ $(SolutionDir)\Build\msys2\usr\bin\lemon %(FullPath) -T$(SolutionDir)\Build\msys2\usr\share\lemon\lempar.c
+ %(RelativeDir)\FilterParser.c
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Src/FilterEngine/FilterEngine.vcxitems.filters b/Src/FilterEngine/FilterEngine.vcxitems.filters
new file mode 100644
index 00000000000..56935f5e02a
--- /dev/null
+++ b/Src/FilterEngine/FilterEngine.vcxitems.filters
@@ -0,0 +1,59 @@
+
+
+
+
+ {cbb36660-3f8a-46ba-855a-fa8f32b99d72}
+
+
+ {7e65cd72-3165-42cc-b722-0dcf1593ffdd}
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/Src/FilterEngine/FilterError.h b/Src/FilterEngine/FilterError.h
new file mode 100644
index 00000000000..52cc6498528
--- /dev/null
+++ b/Src/FilterEngine/FilterError.h
@@ -0,0 +1,19 @@
+/**
+ * @file FilterError.h
+ * @brief Defines error codes for the filter engine.
+ */
+#pragma once
+
+enum FilterErrorCode
+{
+ FILTER_ERROR_NO_ERROR = 0,
+ FILTER_ERROR_UNKNOWN_CHAR = 1,
+ FILTER_ERROR_UNTERMINATED_STRING = 2,
+ FILTER_ERROR_SYNTAX_ERROR = 3,
+ FILTER_ERROR_PARSE_FAILURE = 4,
+ FILTER_ERROR_INVALID_LITERAL = 5,
+ FILTER_ERROR_INVALID_REGULAR_EXPRESSION = 6,
+ FILTER_ERROR_UNDEFINED_IDENTIFIER = 7,
+ FILTER_ERROR_INVALID_ARGUMENT_COUNT = 8,
+ FILTER_ERROR_FILTER_NAME_NOT_FOUND = 9,
+};
diff --git a/Src/FilterEngine/FilterExpression.cpp b/Src/FilterEngine/FilterExpression.cpp
new file mode 100644
index 00000000000..576c88ab105
--- /dev/null
+++ b/Src/FilterEngine/FilterExpression.cpp
@@ -0,0 +1,130 @@
+/**
+ * @file FilterExpression.cpp
+ *
+ * @brief Filter engine implementation.
+ */
+#include "pch.h"
+#include "FilterExpression.h"
+#include "FilterExpressionNodes.h"
+#include "FilterLexer.h"
+#include "DiffContext.h"
+#include "DiffItem.h"
+#include
+
+extern void Parse(void* yyp, int yymajor, YYSTYPE yyminor, FilterExpression* pCtx);
+extern void* ParseAlloc(void* (*mallocProc)(size_t));
+extern void ParseFree(void* yyp, void (*freeProc)(void*));
+
+void YYSTYPEDestructor(YYSTYPE& yystype)
+{
+ if (yystype.nodeList)
+ {
+ for (auto& node : *yystype.nodeList)
+ delete node;
+ delete yystype.nodeList;
+ }
+ delete yystype.node;
+ yystype.node = nullptr;
+ yystype.nodeList = nullptr;
+}
+
+FilterExpression::FilterExpression()
+{
+}
+
+FilterExpression::FilterExpression(const FilterExpression& other)
+ : optimize(other.optimize)
+ , ctxt(other.ctxt)
+ , now(other.now ? new Poco::Timestamp(*other.now) : nullptr)
+ , today(other.today ? new Poco::Timestamp(*other.today) : nullptr)
+ , expression(other.expression)
+{
+ Parse(expression);
+}
+
+FilterExpression::FilterExpression(const std::string& expression)
+{
+ Parse(expression);
+}
+
+FilterExpression::~FilterExpression()
+{
+ Clear();
+}
+
+void FilterExpression::Clear()
+{
+ now.reset();
+ today.reset();
+ rootNode.reset();
+ errorCode = FILTER_ERROR_NO_ERROR;
+ errorPosition = -1;
+}
+
+void FilterExpression::UpdateTimestamp()
+{
+ now.reset(new Poco::Timestamp());
+ Poco::LocalDateTime ldt(*now);
+ Poco::LocalDateTime midnight(ldt.year(), ldt.month(), ldt.day(), 0, 0, 0);
+ today.reset(new Poco::Timestamp(midnight.timestamp()));
+}
+
+bool FilterExpression::Parse()
+{
+ Clear();
+ UpdateTimestamp();
+ FilterLexer lexer(expression);
+ void* prs = ParseAlloc(malloc);
+ int token;
+ FilterErrorCode firstError = FILTER_ERROR_NO_ERROR;
+ while ((token = lexer.yylex()) != 0)
+ {
+ if (token < 0)
+ {
+ firstError = static_cast(-token);
+ errorPosition = static_cast(lexer.yycursor - expression.c_str());
+ break;
+ }
+ ::Parse(prs, token, lexer.yylval, this);
+ if (errorCode != 0)
+ {
+ firstError = errorCode;
+ errorPosition = static_cast(lexer.yycursor - expression.c_str());
+ break;
+ }
+ lexer.yycursor = lexer.YYCURSOR;
+ }
+ ::Parse(prs, 0, lexer.yylval, this);
+ if (firstError == 0 && errorCode != 0)
+ {
+ firstError = errorCode;
+ errorPosition = static_cast(lexer.yycursor - expression.c_str());
+ }
+ ::ParseFree(prs, free);
+ if (firstError != 0)
+ errorCode = firstError;
+ return (errorCode == 0 && rootNode != nullptr);
+
+}
+
+bool FilterExpression::Parse(const std::string& expressionStr)
+{
+ expression = expressionStr;
+ return Parse();
+}
+
+bool FilterExpression::Evaluate(const DIFFITEM& di)
+{
+ const auto result = rootNode->Evaluate(di);
+ if (const auto boolVal = std::get_if(&result))
+ return *boolVal;
+ if (const auto arrayVal = std::get_if>>(&result))
+ {
+ const auto& vec = *arrayVal->get();
+ return std::any_of(vec.begin(), vec.end(), [](const ValueType2& item) {
+ const auto boolVal = std::get_if(&item.value);
+ return boolVal && *boolVal;
+ });
+ }
+ return false;
+}
diff --git a/Src/FilterEngine/FilterExpression.h b/Src/FilterEngine/FilterExpression.h
new file mode 100644
index 00000000000..139e97622e5
--- /dev/null
+++ b/Src/FilterEngine/FilterExpression.h
@@ -0,0 +1,39 @@
+/**
+ * @file FilterExpression.h
+ *
+ * @brief Header file for the FilterExpression class, which provides functionality to parse and evaluate filter expressions.
+ */
+#pragma once
+
+#include
+#include
+#include "FilterError.h"
+
+class CDiffContext;
+class DIFFITEM;
+struct ExprNode;
+struct YYSTYPE;
+namespace Poco { class Timestamp; }
+
+struct FilterExpression
+{
+ FilterExpression();
+ FilterExpression(const FilterExpression& other);
+ FilterExpression(const std::string& expression);
+ ~FilterExpression();
+ bool Parse(const std::string& expression);
+ bool Parse();
+ void SetDiffContext(const CDiffContext* pCtxt) { ctxt = pCtxt; }
+ bool Evaluate(const DIFFITEM& di);
+ void UpdateTimestamp();
+ void Clear();
+ bool optimize = true;
+ const CDiffContext* ctxt = nullptr;
+ std::unique_ptr now;
+ std::unique_ptr today;
+ std::unique_ptr rootNode;
+ std::string expression;
+ FilterErrorCode errorCode = FILTER_ERROR_NO_ERROR;
+ int errorPosition = -1;
+ std::string errorMessage;
+};
diff --git a/Src/FilterEngine/FilterExpressionNodes.cpp b/Src/FilterEngine/FilterExpressionNodes.cpp
new file mode 100644
index 00000000000..ff3b154ae43
--- /dev/null
+++ b/Src/FilterEngine/FilterExpressionNodes.cpp
@@ -0,0 +1,1103 @@
+/**
+ * @file FilterExpressionNodes.cpp
+ *
+ * @brief This file implements the filter expression evaluation logic for the FilterExpression.
+ */
+#include "pch.h"
+#include "FilterExpressionNodes.h"
+#include "FilterExpression.h"
+#include "FileContentRef.h"
+#include "DiffContext.h"
+#include "DiffItem.h"
+#include "paths.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static std::optional evalAsBool(const ValueType& val)
+{
+ auto boolVal = std::get_if(&val);
+ if (boolVal) return *boolVal;
+
+ auto ary = std::get_if>>(&val);
+ if (ary)
+ {
+ const auto& vec = *ary->get();
+ return std::any_of(vec.begin(), vec.end(), [](const ValueType2& item) {
+ const auto boolVal = std::get_if(&item.value);
+ return boolVal && *boolVal;
+ });
+ }
+ return std::nullopt;
+}
+
+ExprNode* OrNode::Optimize()
+{
+ if (!left || !right)
+ return this;
+ left = left->Optimize();
+ right = right->Optimize();
+ auto lBool = dynamic_cast(left);
+ auto rBool = dynamic_cast(right);
+ if (lBool && rBool)
+ {
+ const bool result = lBool->value || rBool->value;
+ delete this;
+ return new BoolLiteral(result);
+ }
+ return this;
+}
+
+ValueType OrNode::Evaluate(const DIFFITEM& di) const
+{
+ auto lval = left->Evaluate(di);
+ auto lbool = evalAsBool(lval);
+ if (lbool && *lbool) return true;
+
+ auto rval = right->Evaluate(di);
+ auto rbool = evalAsBool(rval);
+ if (rbool && *rbool) return true;
+
+ if (lbool || rbool) return false;
+ return std::monostate{};
+}
+
+ExprNode* AndNode::Optimize()
+{
+ if (!left || !right)
+ return this;
+ left = left->Optimize();
+ right = right->Optimize();
+ auto lBool = dynamic_cast(left);
+ auto rBool = dynamic_cast(right);
+ if (lBool && rBool)
+ {
+ const bool result = lBool->value && rBool->value;
+ delete this;
+ return new BoolLiteral(result);
+ }
+ return this;
+}
+
+ValueType AndNode::Evaluate(const DIFFITEM& di) const
+{
+ auto lval = left->Evaluate(di);
+ auto lbool = evalAsBool(lval);
+ if (!lbool) return std::monostate{};
+ if (!*lbool) return false;
+
+ auto rval = right->Evaluate(di);
+ auto rbool = evalAsBool(rval);
+ if (!rbool) return std::monostate{};
+ if (!*rbool) return false;
+ return true;
+}
+
+ExprNode* NotNode::Optimize()
+{
+ if (!expr)
+ return this;
+ expr = expr->Optimize();
+ auto boolVal = dynamic_cast(expr);
+ if (boolVal)
+ {
+ const bool result = !boolVal->value;
+ delete this;
+ return new BoolLiteral(result);
+ }
+ return this;
+}
+
+ValueType NotNode::Evaluate(const DIFFITEM& di) const
+{
+ auto val = expr->Evaluate(di);
+ auto boolVal = evalAsBool(val);
+ if (!boolVal) return std::monostate{};
+ return !*boolVal;
+}
+
+static std::optional getConstIntValue(const ExprNode* node)
+{
+ if (auto intNode = dynamic_cast(node))
+ return intNode->value;
+ if (auto sizeNode = dynamic_cast(node))
+ return sizeNode->value;
+ if (auto durationNode = dynamic_cast(node))
+ return durationNode->value;
+ if (auto versionNode = dynamic_cast(node))
+ return versionNode->value;
+ return std::nullopt;
+}
+
+/**
+ * @brief Attempts to fold constants in an expression tree.
+ *
+ * This function evaluates constant expressions involving two nodes and an operator.
+ * If both nodes represent constant values, it computes the result and returns a new
+ * literal node representing the folded constant. Otherwise, it returns nullptr.
+ *
+ * @param left The left-hand side expression node.
+ * @param op The operator to apply (e.g., TK_PLUS, TK_EQ).
+ * @param right The right-hand side expression node.
+ * @return A new ExprNode representing the folded constant, or nullptr if folding is not possible.
+ */
+static ExprNode* TryFoldConstants(ExprNode* left, int op, ExprNode* right)
+{
+ // Extract constant integer values from the left and right nodes.
+ auto lInt = getConstIntValue(left);
+ auto rInt = getConstIntValue(right);
+
+ // If both nodes are constants, attempt to fold them.
+ if (lInt && rInt)
+ {
+ // Handle comparison operators (e.g., ==, !=, <, <=, >, >=).
+ if (op >= TK_EQ && op <= TK_GE)
+ {
+ bool result;
+ switch (op)
+ {
+ case TK_EQ: result = *lInt == *rInt; break;
+ case TK_NE: result = *lInt != *rInt; break;
+ case TK_LT: result = *lInt < *rInt; break;
+ case TK_LE: result = *lInt <= *rInt; break;
+ case TK_GT: result = *lInt > *rInt; break;
+ case TK_GE: result = *lInt >= *rInt; break;
+ default: return nullptr; // Invalid operator.
+ }
+ // Return a boolean literal representing the comparison result.
+ return new BoolLiteral(result);
+ }
+
+ // Handle arithmetic operators (e.g., +, -, *, /, %).
+ int64_t result = 0;
+ switch (op)
+ {
+ case TK_PLUS: result = *lInt + *rInt; break;
+ case TK_MINUS: result = *lInt - *rInt; break;
+ case TK_STAR: result = *lInt * *rInt; break;
+ case TK_SLASH:
+ // Avoid division by zero.
+ if (*rInt != 0) result = *lInt / *rInt; else return nullptr;
+ break;
+ case TK_MOD:
+ // Avoid modulo by zero.
+ if (*rInt != 0) result = *lInt % *rInt; else return nullptr;
+ break;
+ default: return nullptr; // Invalid operator.
+ }
+ // Return an integer literal representing the arithmetic result.
+ return new IntLiteral(result);
+ }
+ auto lStr = dynamic_cast(left);
+ auto rStr = dynamic_cast(right);
+ if (lStr && rStr)
+ {
+ if (op >= TK_EQ && op <= TK_GE)
+ {
+ bool result = false;
+ switch (op)
+ {
+ case TK_EQ: result = Poco::icompare(lStr->value, rStr->value) == 0; break;
+ case TK_NE: result = Poco::icompare(lStr->value, rStr->value) != 0; break;
+ case TK_LT: result = Poco::icompare(lStr->value, rStr->value) < 0; break;
+ case TK_LE: result = Poco::icompare(lStr->value, rStr->value) <= 0; break;
+ case TK_GT: result = Poco::icompare(lStr->value, rStr->value) > 0; break;
+ case TK_GE: result = Poco::icompare(lStr->value, rStr->value) >= 0; break;
+ }
+ return new BoolLiteral(result);
+ }
+
+ std::string result;
+ switch (op)
+ {
+ case TK_PLUS: result = lStr->value + rStr->value; break;
+ default: return nullptr;
+ }
+ return new StringLiteral(result);
+ }
+ auto lBool = dynamic_cast(left);
+ auto rBool = dynamic_cast(right);
+ if (lBool && rBool)
+ {
+ if (op >= TK_EQ && op <= TK_GE)
+ {
+ bool result = false;
+ switch (op)
+ {
+ case TK_EQ: result = lBool->value == rBool->value; break;
+ case TK_NE: result = lBool->value != rBool->value; break;
+ case TK_LT: result = lBool->value < rBool->value; break;
+ case TK_LE: result = lBool->value <= rBool->value; break;
+ case TK_GT: result = lBool->value > rBool->value; break;
+ case TK_GE: result = lBool->value >= rBool->value; break;
+ default: return nullptr;
+ }
+ return new BoolLiteral(result);
+ }
+
+ int64_t result;
+ switch (op)
+ {
+ case TK_PLUS: result = lBool->value + rBool->value; break;
+ case TK_MINUS: result = lBool->value - rBool->value; break;
+ default: return nullptr;
+ }
+ return new IntLiteral(result);
+ }
+ auto lDateTime = dynamic_cast(left);
+ auto rDateTime = dynamic_cast(right);
+ if (lDateTime && rDateTime)
+ {
+ if (op >= TK_EQ && op <= TK_GE)
+ {
+ bool result = false;
+ switch (op)
+ {
+ case TK_EQ: result = lDateTime->value == rDateTime->value; break;
+ case TK_NE: result = lDateTime->value != rDateTime->value; break;
+ case TK_LT: result = lDateTime->value < rDateTime->value; break;
+ case TK_LE: result = lDateTime->value <= rDateTime->value; break;
+ case TK_GT: result = lDateTime->value > rDateTime->value; break;
+ case TK_GE: result = lDateTime->value >= rDateTime->value; break;
+ default: return nullptr;
+ }
+ return new BoolLiteral(result);
+ }
+
+ Poco::Timestamp result;
+ switch (op)
+ {
+ case TK_MINUS: result = lDateTime->value - rDateTime->value; break;
+ default: return nullptr;
+ }
+ return new DateTimeLiteral(result);
+ }
+ if (lDateTime && rInt)
+ {
+ if (op == TK_PLUS) return new DateTimeLiteral(lDateTime->value + *rInt);
+ if (op == TK_MINUS) return new DateTimeLiteral(lDateTime->value - *rInt);
+ }
+ return nullptr;
+}
+
+ExprNode* BinaryOpNode::Optimize()
+{
+ if (!left || !right)
+ return this;
+ left = left->Optimize();
+ right = right->Optimize();
+ if (ExprNode* folded = TryFoldConstants(left, op, right))
+ {
+ delete this;
+ return folded;
+ }
+ if (op == TK_RECONTAINS || op == TK_MATCHES)
+ {
+ if (auto strNode = dynamic_cast(right))
+ {
+ right = new RegularExpressionLiteral(strNode->value);
+ delete strNode;
+ }
+ }
+ return this;
+}
+
+ValueType BinaryOpNode::Evaluate(const DIFFITEM& di) const
+{
+ auto lval = left->Evaluate(di);
+ auto rval = right->Evaluate(di);
+ auto compute = [](int op, const ValueType& lval, const ValueType& rval) -> ValueType
+ {
+ if (auto lvalInt = std::get_if(&lval))
+ {
+ if (auto rvalInt = std::get_if(&rval))
+ {
+ if (op == TK_EQ) return *lvalInt == *rvalInt;
+ if (op == TK_NE) return *lvalInt != *rvalInt;
+ if (op == TK_LT) return *lvalInt < *rvalInt;
+ if (op == TK_LE) return *lvalInt <= *rvalInt;
+ if (op == TK_GT) return *lvalInt > *rvalInt;
+ if (op == TK_GE) return *lvalInt >= *rvalInt;
+ if (op == TK_PLUS) return *lvalInt + *rvalInt;
+ if (op == TK_MINUS) return *lvalInt - *rvalInt;
+ if (op == TK_STAR) return *lvalInt * *rvalInt;
+ if (op == TK_SLASH) return *lvalInt / *rvalInt;
+ if (op == TK_MOD) return *lvalInt % *rvalInt;
+ }
+ }
+ else if (auto lvalTimestamp = std::get_if(&lval))
+ {
+ if (auto rvalTimestamp = std::get_if(&rval))
+ {
+ if (op == TK_EQ) return *lvalTimestamp == *rvalTimestamp;
+ if (op == TK_NE) return *lvalTimestamp != *rvalTimestamp;
+ if (op == TK_LT) return *lvalTimestamp < *rvalTimestamp;
+ if (op == TK_LE) return *lvalTimestamp <= *rvalTimestamp;
+ if (op == TK_GT) return *lvalTimestamp > *rvalTimestamp;
+ if (op == TK_GE) return *lvalTimestamp >= *rvalTimestamp;
+ if (op == TK_MINUS) return *lvalTimestamp - *rvalTimestamp;
+ }
+ else if (auto rvalInt = std::get_if(&rval))
+ {
+ if (op == TK_PLUS) return *lvalTimestamp + *rvalInt;
+ if (op == TK_MINUS) return *lvalTimestamp - *rvalInt;
+ }
+ }
+ else if (auto lvalString = std::get_if(&lval))
+ {
+ if (auto rvalString = std::get_if(&rval))
+ {
+ if (op == TK_EQ) return Poco::icompare(*lvalString, *rvalString) == 0;
+ if (op == TK_NE) return Poco::icompare(*lvalString, *rvalString) != 0;
+ if (op == TK_LT) return Poco::icompare(*lvalString, *rvalString) < 0;
+ if (op == TK_LE) return Poco::icompare(*lvalString, *rvalString) <= 0;
+ if (op == TK_GT) return Poco::icompare(*lvalString, *rvalString) > 0;
+ if (op == TK_GE) return Poco::icompare(*lvalString, *rvalString) >= 0;
+ if (op == TK_PLUS) return *rvalString + *lvalString;
+ if (op == TK_CONTAINS)
+ {
+ auto searcher = std::boyer_moore_horspool_searcher(
+ rvalString->cbegin(), rvalString->cend(), std::hash(),
+ [](char a, char b) {
+ return std::tolower(static_cast(a)) ==
+ std::tolower(static_cast(b));
+ }
+ );
+ using iterator = std::string::const_iterator;
+ std::pair result = searcher(lvalString->begin(), lvalString->end());
+ return (result.first != result.second);
+ }
+ if (op == TK_RECONTAINS)
+ {
+ try
+ {
+ Poco::RegularExpression regex(*rvalString, Poco::RegularExpression::RE_CASELESS | Poco::RegularExpression::RE_UTF8);
+ Poco::RegularExpression::Match match;
+ return (regex.match(*lvalString, match) > 0);
+ }
+ catch (const Poco::RegularExpressionException&)
+ {
+ return false;
+ }
+ }
+ if (op == TK_MATCHES)
+ {
+ try
+ {
+ Poco::RegularExpression regex(*rvalString, Poco::RegularExpression::RE_CASELESS | Poco::RegularExpression::RE_UTF8);
+ return regex.match(*lvalString);
+ }
+ catch (const Poco::RegularExpressionException&)
+ {
+ return false;
+ }
+ }
+ }
+ if (auto rvalRegexp = std::get_if>(&rval))
+ {
+ if (op == TK_RECONTAINS)
+ {
+ try
+ {
+ Poco::RegularExpression::Match match;
+ return (rvalRegexp->get()->match(*lvalString, match) > 0);
+ }
+ catch (const Poco::RegularExpressionException&)
+ {
+ return false;
+ }
+ }
+ if (op == TK_MATCHES)
+ return rvalRegexp->get()->match(*lvalString);
+ }
+ }
+ else if (auto lvalContent = std::get_if>(&lval))
+ {
+ if (auto rvalContent = std::get_if>(&rval))
+ {
+ if (op == TK_EQ) return lvalContent->get()->operator==(*rvalContent->get());
+ if (op == TK_NE) return !(lvalContent->get()->operator==(*rvalContent->get()));
+ }
+ if (auto rvalString = std::get_if(&rval))
+ {
+ if (op == TK_CONTAINS) return lvalContent->get()->Contains(*rvalString);
+ if (op == TK_RECONTAINS)
+ {
+ try
+ {
+ Poco::RegularExpression regex(*rvalString, Poco::RegularExpression::RE_CASELESS | Poco::RegularExpression::RE_UTF8);
+ return lvalContent->get()->REContains(regex);
+ }
+ catch (const Poco::RegularExpressionException&)
+ {
+ return false;
+ }
+ }
+ }
+ if (auto rvalRegexp = std::get_if>(&rval))
+ {
+ if (op == TK_RECONTAINS) return lvalContent->get()->REContains(*rvalRegexp->get());
+ }
+ }
+ else if (auto lvalBool = std::get_if(&lval))
+ {
+ if (auto rvalBool = std::get_if(&rval))
+ {
+ if (op == TK_EQ) return *lvalBool == *rvalBool;
+ if (op == TK_NE) return *lvalBool != *rvalBool;
+ if (op == TK_LT) return *lvalBool < *rvalBool;
+ if (op == TK_LE) return *lvalBool <= *rvalBool;
+ if (op == TK_GT) return *lvalBool > *rvalBool;
+ if (op == TK_GE) return *lvalBool >= *rvalBool;
+ if (op == TK_PLUS) return static_cast(*rvalBool + *lvalBool);
+ if (op == TK_MINUS) return static_cast(*rvalBool - *lvalBool);
+ }
+ }
+ if (op == TK_EQ)
+ return false;
+ else if (op == TK_NE)
+ return true;
+ return std::monostate{};
+ };
+ auto lvalArray = std::get_if>>(&lval);
+ auto rvalArray = std::get_if>>(&rval);
+ if (!lvalArray && !rvalArray)
+ {
+ return compute(op, lval, rval);
+ }
+ else if (lvalArray && !rvalArray)
+ {
+ std::unique_ptr> result = std::make_unique>();
+ for (const auto& item : *(lvalArray->get()))
+ result->emplace_back(ValueType2{ compute(op, item.value, rval) });
+ return result;
+ }
+ else if (!lvalArray && rvalArray)
+ {
+ std::unique_ptr> result = std::make_unique>();
+ for (const auto& item : *(rvalArray->get()))
+ result->emplace_back(ValueType2{ compute(op, lval, item.value) });
+ return result;
+ }
+ else
+ {
+ const size_t maxSize = (std::max)(lvalArray->get()->size(), rvalArray->get()->size());
+ const size_t minSize = (std::min)(lvalArray->get()->size(), rvalArray->get()->size());
+ std::unique_ptr> result = std::make_unique>();
+ for (size_t i = 0; i < minSize; ++i)
+ result->emplace_back(ValueType2{ compute(op, lvalArray->get()->at(i).value, rvalArray->get()->at(i).value) });
+ for (size_t i = 0; i < maxSize - minSize; ++i)
+ result->emplace_back(ValueType2{ std::monostate{} });
+ return result;
+ }
+}
+
+ExprNode* NegateNode::Optimize()
+{
+ if (!right)
+ return this;
+ right = right->Optimize();
+ auto rInt = getConstIntValue(right);
+ if (rInt)
+ {
+ delete this;
+ return new IntLiteral(-*rInt);
+ }
+ return this;
+}
+
+ValueType NegateNode::Evaluate(const DIFFITEM& di) const
+{
+ auto rval = right->Evaluate(di);
+ if (auto rvalInt = std::get_if(&rval))
+ return -*rvalInt;
+ return std::monostate{};
+}
+
+static auto ExistsField(int index, const FilterExpression* ctxt, const DIFFITEM& di)-> ValueType
+{
+ return di.diffcode.exists(index);
+}
+
+static auto NameField(int index, const FilterExpression* ctxt, const DIFFITEM& di)-> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ return ucr::toUTF8(di.diffFileInfo[index].filename.get());
+}
+
+static auto ExtensionField(int index, const FilterExpression* ctxt, const DIFFITEM& di)-> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ const std::string ext = ucr::toUTF8(paths::FindExtension(di.diffFileInfo[index].filename.get()));
+ return std::string(ext.c_str() + strspn(ext.c_str(), "."));
+}
+
+static auto FolderField(int index, const FilterExpression* ctxt, const DIFFITEM& di)-> ValueType
+{
+ return ucr::toUTF8(di.diffFileInfo[index].path.get());
+}
+
+static auto SizeField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ return static_cast(di.diffFileInfo[index].size);
+}
+
+static auto DateField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ return di.diffFileInfo[index].mtime;
+}
+
+static auto DateStrField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ Poco::LocalDateTime ldt(Poco::Timezone::tzd(), di.diffFileInfo[index].mtime);
+ return Poco::DateTimeFormatter::format(ldt, "%Y-%m-%d");
+}
+
+static auto CreationTimeField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ return di.diffFileInfo[index].ctime;
+}
+
+static auto FileVersionField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ if (di.diffFileInfo[index].version.IsCleared())
+ ctxt->ctxt->UpdateVersion(const_cast(di), index);
+ return static_cast(di.diffFileInfo[index].version.GetFileVersionQWORD());
+}
+
+static auto AttributesField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ return static_cast(di.diffFileInfo[index].flags.attributes);
+}
+
+static auto AttrStrField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ return ucr::toUTF8(di.diffFileInfo[index].flags.ToString());
+}
+
+static auto CodepageField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ return static_cast(di.diffFileInfo[index].encoding.m_codepage);
+}
+
+static auto DiffCodeField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ return static_cast(di.diffcode.diffcode);
+}
+
+static auto DifferencesField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ return static_cast(di.nsdiffs);
+}
+
+static auto IgnoredDiffsField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ return static_cast(di.nidiffs);
+}
+
+static auto EncodingField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ return ucr::toUTF8(di.diffFileInfo[index].encoding.GetName());
+}
+
+static auto FullPathField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ const String relpath = paths::ConcatPath(di.diffFileInfo[index].path, di.diffFileInfo[index].filename);
+ return ucr::toUTF8(paths::ConcatPath(ctxt->ctxt->GetPath(index), relpath));
+}
+
+static auto ContentField(int index, const FilterExpression* ctxt, const DIFFITEM& di) -> ValueType
+{
+ if (!di.diffcode.exists(index))
+ return std::monostate{};
+ const String relpath = paths::ConcatPath(di.diffFileInfo[index].path, di.diffFileInfo[index].filename);
+ std::shared_ptr content{ new FileContentRef };
+ content->path = paths::ConcatPath(ctxt->ctxt->GetPath(index), relpath);
+ content->item.size = di.diffFileInfo[index].size;
+ content->item.flags = di.diffFileInfo[index].flags;
+ content->item.mtime = di.diffFileInfo[index].mtime;
+ content->item.ctime = di.diffFileInfo[index].ctime;
+ content->item.version = di.diffFileInfo[index].version;
+ content->item.encoding = di.diffFileInfo[index].encoding;
+ return content;
+}
+
+FieldNode::FieldNode(const FilterExpression* ctxt, const std::string& v) : ctxt(ctxt), field(v)
+{
+ int prefixlen = 0;
+ int side = 0;
+ std::string vl = Poco::toLower(v);
+ if (vl.compare(0, 4, "left") == 0)
+ {
+ side = 0;
+ prefixlen = 4;
+ }
+ else if (vl.compare(0, 6, "middle") == 0)
+ {
+ side = 1;
+ prefixlen = 6;
+ }
+ else if (vl.compare(0, 5, "right") == 0)
+ {
+ side = -1;
+ prefixlen = 5;
+ }
+ ValueType (*functmp)(int, const FilterExpression*, const DIFFITEM&) = nullptr;
+ const char* p = vl.c_str() + prefixlen;
+ if (strcmp(p, "exists") == 0)
+ functmp = ExistsField;
+ else if (strcmp(p, "name") == 0)
+ functmp = NameField;
+ else if (strcmp(p, "extension") == 0)
+ functmp = ExtensionField;
+ else if (strcmp(p, "fullpath") == 0)
+ functmp = FullPathField;
+ else if (strcmp(p, "folder") == 0)
+ functmp = FolderField;
+ else if (strcmp(p, "size") == 0)
+ functmp = SizeField;
+ else if (strcmp(p, "datestr") == 0)
+ functmp = DateStrField;
+ else if (strcmp(p, "date") == 0)
+ functmp = DateField;
+ else if (strcmp(p, "attributes") == 0)
+ functmp = AttributesField;
+ else if (strcmp(p, "attrstr") == 0)
+ functmp = AttrStrField;
+ else if (strcmp(p, "creationtime") == 0)
+ functmp = CreationTimeField;
+ else if (strcmp(p, "version") == 0)
+ functmp = FileVersionField;
+ else if (strcmp(p, "codepage") == 0)
+ functmp = CodepageField;
+ else if (strcmp(p, "encoding") == 0)
+ functmp = EncodingField;
+ else if (strcmp(p, "diffcode") == 0)
+ {
+ functmp = DiffCodeField;
+ side = -2;
+ }
+ else if (strcmp(p, "differences") == 0)
+ {
+ functmp = DifferencesField;
+ side = -2;
+ }
+ else if (strcmp(p, "ignoreddiffs") == 0)
+ {
+ functmp = IgnoredDiffsField;
+ side = -2;
+ }
+ else if (strcmp(p, "content") == 0)
+ functmp = ContentField;
+ else
+ throw std::runtime_error("Invalid field name: " + std::string(v.begin(), v.end()));
+ if (prefixlen > 0)
+ func = [side, functmp](const FilterExpression* ctxt, const DIFFITEM& di)-> ValueType { return functmp(side < 0 ? ctxt->ctxt->GetCompareDirs() + side: side, ctxt, di); };
+ else
+ func = [functmp](const FilterExpression* ctxt, const DIFFITEM& di)-> ValueType {
+ const int dirs = ctxt->ctxt->GetCompareDirs();
+ std::unique_ptr> values = std::make_unique>();
+ for (int i = 0; i < dirs; ++i)
+ values->emplace_back(ValueType2{ functmp(i, ctxt, di) });
+ return values;
+ };
+}
+
+ValueType FieldNode::Evaluate(const DIFFITEM& di) const
+{
+ return func(ctxt, di);
+}
+
+static auto AbsFunc(const FilterExpression* ctxt, const DIFFITEM& di, std::vector* args) -> ValueType
+{
+ auto arg1 = (*args)[0]->Evaluate(di);
+ if (auto arg1Int = std::get_if(&arg1))
+ return abs(*arg1Int);
+ return std::monostate{};
+}
+
+static auto AnyOfFunc(const FilterExpression* ctxt, const DIFFITEM& di, std::vector* args) -> ValueType
+{
+ auto arg1 = (*args)[0]->Evaluate(di);
+ if (const auto arrayVal = std::get_if>>(&arg1))
+ {
+ const auto& vec = *arrayVal->get();
+ return std::any_of(vec.begin(), vec.end(), [](const ValueType2& item) {
+ const auto boolVal = std::get_if(&item.value);
+ return boolVal && *boolVal;
+ });
+ }
+ if (auto arg1Bool = std::get_if(&arg1))
+ return *arg1Bool;
+ return std::monostate{};
+}
+
+static auto AllOfFunc(const FilterExpression* ctxt, const DIFFITEM& di, std::vector* args) -> ValueType
+{
+ auto arg1 = (*args)[0]->Evaluate(di);
+ if (const auto arrayVal = std::get_if>>(&arg1))
+ {
+ const auto& vec = *arrayVal->get();
+ return std::all_of(vec.begin(), vec.end(), [](const ValueType2& item) {
+ const auto boolVal = std::get_if(&item.value);
+ return boolVal && *boolVal;
+ });
+ }
+ if (auto arg1Bool = std::get_if(&arg1))
+ return *arg1Bool;
+ return std::monostate{};
+}
+
+static auto AllEqualFunc(const FilterExpression* ctxt, const DIFFITEM& di, std::vector* args) -> ValueType
+{
+ ValueType first = args->at(0)->Evaluate(di);
+ if (auto pArray = std::get_if>>(&first); pArray && *pArray)
+ {
+ const auto& vec = **pArray;
+ if (vec.size() <= 1)
+ return true;
+ const ValueType& base = vec[0].value;
+ for (size_t i = 1; i < vec.size(); ++i)
+ {
+ if (!(vec[i].value == base))
+ return false;
+ }
+ return true;
+ }
+ else
+ {
+ for (size_t i = 1; i < args->size(); ++i)
+ {
+ ValueType val = args->at(i)->Evaluate(di);
+ if (!(val == first))
+ return false;
+ }
+ return true;
+ }
+}
+
+static auto LengthFunc(const FilterExpression* ctxt, const DIFFITEM& di, std::vector* args) -> ValueType
+{
+ auto arg1 = (*args)[0]->Evaluate(di);
+ if (auto arg1String = std::get_if(&arg1))
+ return static_cast(arg1String->length());
+ return std::monostate{};
+}
+
+static auto SubstrFunc(const FilterExpression* ctxt, const DIFFITEM& di, std::vector* args) -> ValueType
+{
+ if (args->size() < 2 || args->size() > 3)
+ return std::monostate{};
+
+ auto argStr = (*args)[0]->Evaluate(di);
+ auto argStart = (*args)[1]->Evaluate(di);
+ std::optional argLen;
+ if (args->size() == 3)
+ argLen = (*args)[2]->Evaluate(di);
+
+ const std::string* str = std::get_if(&argStr);
+ const int64_t* start = std::get_if(&argStart);
+ const int64_t* len = argLen ? std::get_if(&*argLen) : nullptr;
+
+ if (!str || !start)
+ return std::monostate{};
+
+ int64_t s = *start;
+ if (s < 0)
+ s += static_cast(str->length());
+ if (s < 0 || s > str->length())
+ return std::string{};
+
+ if (!len)
+ return str->substr(s);
+ return str->substr(s, static_cast(*len));
+}
+
+static auto LineCountFunc(const FilterExpression* ctxt, const DIFFITEM& di, std::vector* args) -> ValueType
+{
+ auto arg1 = (*args)[0]->Evaluate(di);
+ if (auto arg1ContentRef = std::get_if>(&arg1))
+ return static_cast