From: EugeneLaptev <40354914+EugeneLaptev@users.noreply.github.com> Date: Fri, 8 Jan 2021 22:16:23 +0000 (+0000) Subject: Added a new feature "Ignored Substitutions", which are the changes that will be ignor... X-Git-Tag: v2.16.10~85 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=9fd11671156506cd1cad4cad986fd36f6595fa2f;p=winmerge-jp%2Fwinmerge-jp.git Added a new feature "Ignored Substitutions", which are the changes that will be ignored by diff panes. (#544) Co-authored-by: EugeneSumo <40354914+EugeneSumo@users.noreply.github.com> --- diff --git a/Src/CompareEngines/Wrap_DiffUtils.cpp b/Src/CompareEngines/Wrap_DiffUtils.cpp index 993a0788e..31c9fb54f 100644 --- a/Src/CompareEngines/Wrap_DiffUtils.cpp +++ b/Src/CompareEngines/Wrap_DiffUtils.cpp @@ -75,6 +75,12 @@ void DiffUtils::SetFilterList(FilterList * list) m_pFilterList = list; } +void DiffUtils::SetIgnoredSubstitutionsList(FilterList* list0, FilterList* list1) +{ + m_pIgnoredSubstitutionsList0 = list0; + m_pIgnoredSubstitutionsList1 = list1; +} + /** * @brief Set filedata. * @param [in] items Count of filedata items to set. diff --git a/Src/CompareEngines/Wrap_DiffUtils.h b/Src/CompareEngines/Wrap_DiffUtils.h index 32af659ca..a9772c78b 100644 --- a/Src/CompareEngines/Wrap_DiffUtils.h +++ b/Src/CompareEngines/Wrap_DiffUtils.h @@ -32,6 +32,7 @@ public: ~DiffUtils(); void SetCompareOptions(const CompareOptions & options); void SetFilterList(FilterList * list); + void SetIgnoredSubstitutionsList(FilterList* list0, FilterList* list1); void ClearFilterList(); void SetFileData(int items, file_data *data); int diffutils_compare_files(); @@ -45,6 +46,8 @@ public: private: std::unique_ptr m_pOptions; /**< Compare options for diffutils. */ FilterList * m_pFilterList; /**< Filter list for line filters. */ + FilterList* m_pIgnoredSubstitutionsList0; + FilterList* m_pIgnoredSubstitutionsList1; file_data * m_inf; /**< Compared files data (for diffutils). */ int m_ndiffs; /**< Real diffs found. */ int m_ntrivialdiffs; /**< Ignored diffs found. */ diff --git a/Src/DiffContext.cpp b/Src/DiffContext.cpp index 1edb7ce2c..8e384c368 100644 --- a/Src/DiffContext.cpp +++ b/Src/DiffContext.cpp @@ -44,6 +44,7 @@ CDiffContext::CDiffContext(const PathContext & paths, int compareMethod) , m_piAbortable(nullptr) , m_bStopAfterFirstDiff(false) , m_pFilterList(nullptr) +, m_pTokenListsForIs{ nullptr, nullptr } , m_pContentCompareOptions(nullptr) , m_pQuickCompareOptions(nullptr) , m_pOptions(nullptr) diff --git a/Src/DiffContext.h b/Src/DiffContext.h index 96ef3cd4f..ee05ffc9d 100644 --- a/Src/DiffContext.h +++ b/Src/DiffContext.h @@ -194,6 +194,7 @@ public: bool m_bRecursive; /**< Do we include subfolders to compare? */ bool m_bPluginsEnabled; /**< Are plugins enabled? */ std::unique_ptr m_pFilterList; /**< Filter list for line filters */ + std::unique_ptr m_pTokenListsForIs[2]; /// Two lists for Ignored Substitutions private: /** diff --git a/Src/DiffWrapper.cpp b/Src/DiffWrapper.cpp index aa177f5f9..bcf7c843b 100644 --- a/Src/DiffWrapper.cpp +++ b/Src/DiffWrapper.cpp @@ -47,6 +47,12 @@ #include "parsers/crystallineparser.h" #include "SyntaxColors.h" #include "MergeApp.h" +#include "OptionsMgr.h" +#include "OptionsDef.h" +#include "TokenPairList.h" +#include "stringdiffs.h" +using namespace strdiff; + using Poco::Debugger; using Poco::format; @@ -73,6 +79,7 @@ CDiffWrapper::CDiffWrapper() , m_pDiffList(nullptr) , m_bPathsAreTemp(false) , m_pFilterList(nullptr) +, m_pIgnoredSubstitutionsList{nullptr} , m_bPluginsEnabled(false) , m_status() { @@ -848,6 +855,99 @@ bool CDiffWrapper::RegExpFilter(int StartPos, int EndPos, const file_data *pinf) return linesMatch; } +bool MatchDiffVsIngoredSubstitutions +( + const std::string &fullLine0, + const std::string &fullLine1, + const strdiff::wdiff &diff, + const IgnoredSubstitutionsFilterList &ignoredSubstitutionsList, + const bool optUseRegexpsForIgnoredSubstitutions, + const bool optMatchBothWays +) +{ + int changeStartPos[2] = { diff.begin[0], diff.begin[1] }; + int changeEndPos[2] = { diff.end[0], diff.end[1] }; + int changeLen0 = changeEndPos[0] - changeStartPos[0] + 1; + int changeLen1 = changeEndPos[1] - changeStartPos[1] + 1; + std::string change0 = std::string(fullLine0.c_str() + changeStartPos[0], changeLen0); + std::string change1 = std::string(fullLine1.c_str() + changeStartPos[1], changeLen1); + + size_t numIgnoredSubstitutions = ignoredSubstitutionsList.GetCount(); + + for (int f = 0; f < numIgnoredSubstitutions; f++) + { + const IgnoredSusbstitutionItem& filter = ignoredSubstitutionsList[f]; + // Check if the common prefix and suffix fit into the line around the change + if + ( + changeStartPos[0] < filter.CommonPrefixLength + || changeStartPos[1] < filter.CommonPrefixLength + || changeEndPos[0] >= fullLine0.length() - filter.CommonSuffixLength + || changeEndPos[1] >= fullLine1.length() - filter.CommonSuffixLength + ) + continue; /// This filter does not fit into the line with its suffix and prefix + + // Check if the common prefix and suffix match + bool continueWithNextFilter = false; + for (int p = 1; p <= filter.CommonPrefixLength; p++) + { + char char0 = fullLine0[changeStartPos[0] - p]; + char char1 = fullLine1[changeStartPos[1] - p]; + if (char0 != char1) + { + continueWithNextFilter = true; + break; + } + } + + for (int s = 1; s <= filter.CommonSuffixLength; s++) + { + char char0 = fullLine0[changeEndPos[0] + s]; + char char1 = fullLine1[changeEndPos[1] + s]; + if (char0 != char1) + { + continueWithNextFilter = true; + break; + } + } + + if (continueWithNextFilter) + continue; + + if(optUseRegexpsForIgnoredSubstitutions) + { + + if + ( + ignoredSubstitutionsList.MatchBoth(f, change0, change1) + || + optMatchBothWays + && ignoredSubstitutionsList.MatchBoth(f, change1, change0) + ) + { + return true; /// a match found + } + } + else + { + if + ( + filter.ChangedPart[0].compare(change0) == 0 + && filter.ChangedPart[1].compare(change1) == 0 + || + optMatchBothWays + && filter.ChangedPart[0].compare(change1) == 0 + && filter.ChangedPart[1].compare(change0) == 0 + ) + { + return true; /// a match found + } + } + } + + return false; /// n0 match found +} + /** * @brief Walk the diff utils change script, building the WinMerge list of diff blocks */ @@ -859,6 +959,10 @@ CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript(struct change * script, const struct change *next = script; + const bool optCompletelyBlankOutIgnoredSubstitutions = GetOptionsMgr()->GetBool(OPT_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS); + const bool optMatchBothWays = GetOptionsMgr()->GetBool(OPT_IGNORED_SUBSTITUTIONS_WORK_BOTH_WAYS); + const bool optUseRegexpsForIgnoredSubstitutions = GetOptionsMgr()->GetBool(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS); + while (next != nullptr) { /* Find a set of changes that belong together. */ @@ -937,6 +1041,65 @@ CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript(struct change * script, const op = OP_TRIVIAL; } + /// Handling Ignored Substitutions + if + ( + op == OP_DIFF + //&& next != nullptr + && QtyLinesLeft > 0 + && QtyLinesRight > 0 + && m_files.GetSize() == 2 + && m_pIgnoredSubstitutionsList + && m_pIgnoredSubstitutionsList->GetCount() + ) + { + size_t len = file_data_ary[0].linbuf[thisob->line0 + 1] - file_data_ary[0].linbuf[thisob->line0]; + const char* string = file_data_ary[0].linbuf[thisob->line0]; + size_t stringlen = linelen(string, len); + std::string fullLine0 = std::string(string, stringlen); + + len = file_data_ary[1].linbuf[thisob->line1 + 1] - file_data_ary[1].linbuf[thisob->line1]; + string = file_data_ary[1].linbuf[thisob->line1]; + stringlen = linelen(string, len); + std::string fullLine1 = std::string(string, stringlen); + + bool case_sensitive = false; + bool eol_sensitive = false; + int whitespace = WHITESPACE_IGNORE_CHANGE; + int breakType = 1;// breakType==1 means break also on punctuation + bool byte_level = true; + std::vector worddiffs = ComputeWordDiffs( + ucr::toTString(fullLine0.c_str()), ucr::toTString(fullLine1.c_str()), + case_sensitive, eol_sensitive, whitespace, breakType, byte_level + ); + + if (!worddiffs.empty()) + { + bool lineShouldBeIgnored = true; /// If all changes are ignored the line is ignored + for (std::vector::const_iterator diffIt = worddiffs.begin(); diffIt != worddiffs.end(); ++diffIt) + { + if(!MatchDiffVsIngoredSubstitutions + ( + fullLine0, fullLine1, + *diffIt, + *m_pIgnoredSubstitutionsList, optUseRegexpsForIgnoredSubstitutions, + optMatchBothWays + )) + { + lineShouldBeIgnored = false; + } + } + + if (lineShouldBeIgnored) + { + if(optCompletelyBlankOutIgnoredSubstitutions) + op = OP_NONE; + else + op = OP_TRIVIAL; + } + } + } + AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op); } } @@ -1352,6 +1515,53 @@ void CDiffWrapper::SetFilterList(const FilterList* pFilterList) } } +IgnoredSubstitutionsFilterList* CDiffWrapper::GetIgnoredSubstitutionsList() +{ + return m_pIgnoredSubstitutionsList.get(); +} + +void CDiffWrapper::SetIgnoredSubstitutionsList(const FilterList* pIgnoredSubstitutionsList0, const FilterList* pIgnoredSubstitutionsList1) +{ + m_pIgnoredSubstitutionsList.reset(new IgnoredSubstitutionsFilterList()); + + if (!pIgnoredSubstitutionsList0 || pIgnoredSubstitutionsList0->GetCount() == 0) + { + m_pIgnoredSubstitutionsList.reset(); + return; + } + + const bool optUseRegexpsForIgnoredSubstitutions = GetOptionsMgr()->GetBool(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS); + + for (int f = 0; f < pIgnoredSubstitutionsList0->GetCount(); f++) + { + std::string s0 = (*pIgnoredSubstitutionsList0)[f].filterAsString; + std::string s1 = (*pIgnoredSubstitutionsList1)[f].filterAsString; + m_pIgnoredSubstitutionsList->Add(s0, s1, !optUseRegexpsForIgnoredSubstitutions); + } +} + +void CDiffWrapper::SetIgnoredSubstitutionsList(const TokenPairList *ignoredSubstitutionsList) +{ + m_pIgnoredSubstitutionsList.reset(new IgnoredSubstitutionsFilterList()); + + // Remove filterlist if new filter is empty + if (!ignoredSubstitutionsList || ignoredSubstitutionsList->GetCount() == 0) + { + m_pIgnoredSubstitutionsList.reset(); + return; + } + + const bool optUseRegexpsForIgnoredSubstitutions = GetOptionsMgr()->GetBool(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS); + + for (int f = 0; f < ignoredSubstitutionsList->GetCount(); f++) + { + std::string s0 = ucr::toUTF8(ignoredSubstitutionsList->GetAt(f).filterStr0.c_str()); + std::string s1 = ucr::toUTF8(ignoredSubstitutionsList->GetAt(f).filterStr1.c_str()); + if(s0 != s1) + m_pIgnoredSubstitutionsList->Add(s0, s1, !optUseRegexpsForIgnoredSubstitutions); + } +} + void CDiffWrapper::SetFilterCommentsSourceDef(const String& ext) { m_pFilterCommentsDef = CrystalLineParser::GetTextType(ext.c_str()); diff --git a/Src/DiffWrapper.h b/Src/DiffWrapper.h index c3efa65f8..c04858e8a 100644 --- a/Src/DiffWrapper.h +++ b/Src/DiffWrapper.h @@ -24,6 +24,7 @@ class PathContext; struct file_data; class MovedLines; class FilterList; +class IgnoredSubstitutionsFilterList; namespace CrystalLineParser { struct TextDefinition; }; /** @enum COMPARE_TYPE @@ -183,6 +184,9 @@ public: void WritePatchFileTerminator(enum output_style output_style); void SetFilterList(const String& filterStr); void SetFilterList(const FilterList *pFilterList); + IgnoredSubstitutionsFilterList *GetIgnoredSubstitutionsList(); + void SetIgnoredSubstitutionsList(const FilterList* pIgnoredSubstitutionsList0, const FilterList* pIgnoredSubstitutionsList1); + void SetIgnoredSubstitutionsList(const class TokenPairList *ignoredSubstitutionsList); void SetFilterCommentsSourceDef(CrystalLineParser::TextDefinition *def) { m_pFilterCommentsDef = def; }; void SetFilterCommentsSourceDef(const String& ext); void EnablePlugins(bool enable); @@ -202,10 +206,17 @@ public: static void FreeDiffUtilsScript(struct change * & script); bool RegExpFilter(int StartPos, int EndPos, const file_data * pinf) const; + IgnoredSubstitutionsFilterList *GetIgnoredSubstitutionsList(int index) const + { + return m_pIgnoredSubstitutionsList.get(); + } + private: DiffutilsOptions m_options; DIFFSTATUS m_status; /**< Status of last compare */ std::unique_ptr m_pFilterList; /**< List of linefilters. */ + std::unique_ptr m_pIgnoredSubstitutionsList; + PathContext m_files; /**< Full path to diff'ed file. */ PathContext m_alternativePaths; /**< file's alternative path (may be relative). */ PathContext m_originalFile; /**< file's original (NON-TEMP) path. */ diff --git a/Src/DirDoc.cpp b/Src/DirDoc.cpp index 5531f1c2a..bbf75f587 100644 --- a/Src/DirDoc.cpp +++ b/Src/DirDoc.cpp @@ -30,6 +30,7 @@ #include "OptionsMgr.h" #include "OptionsDiffOptions.h" #include "LineFiltersList.h" +#include "TokenPairList.h" #include "FileFilterHelper.h" #include "unicoder.h" #include "DirActions.h" @@ -199,6 +200,36 @@ void CDirDoc::LoadLineFilterList(CDiffContext *pCtxt) pCtxt->m_pFilterList->AddRegExp(*it); } +void CDirDoc::LoadTokensForIgnoredSubstitutions(CDiffContext* pCtxt) +{ + ASSERT(pCtxt != nullptr); + + bool ignoredSubstitutionsAreEnabled = GetOptionsMgr()->GetBool(OPT_IGNORED_SUBSTITUTIONS_ARE_ENABLED); + if (!ignoredSubstitutionsAreEnabled || theApp.m_pTokensForIs->GetCount() == 0) + { + pCtxt->m_pTokenListsForIs[0].reset(); + pCtxt->m_pTokenListsForIs[1].reset(); + return; + } + + if (pCtxt->m_pTokenListsForIs[0]) + pCtxt->m_pTokenListsForIs[0]->RemoveAllFilters(); + else + pCtxt->m_pTokenListsForIs[0].reset(new FilterList()); + + if (pCtxt->m_pTokenListsForIs[1]) + pCtxt->m_pTokenListsForIs[1]->RemoveAllFilters(); + else + pCtxt->m_pTokenListsForIs[1].reset(new FilterList()); + + for (int f = 0; f < theApp.m_pTokensForIs->GetCount(); f++) + { + const TokenPair &tokenPair = theApp.m_pTokensForIs->GetAt(f); + pCtxt->m_pTokenListsForIs[0]->AddRegExp(ucr::toUTF8(tokenPair.filterStr0)); + pCtxt->m_pTokenListsForIs[1]->AddRegExp(ucr::toUTF8(tokenPair.filterStr1)); + } +} + void CDirDoc::DiffThreadCallback(int& state) { PostMessage(m_pDirView->GetSafeHwnd(), MSG_UI_UPDATE, state, false); @@ -207,6 +238,7 @@ void CDirDoc::DiffThreadCallback(int& state) void CDirDoc::InitDiffContext(CDiffContext *pCtxt) { LoadLineFilterList(pCtxt); + LoadTokensForIgnoredSubstitutions(pCtxt); DIFFOPTIONS options = {0}; Options::DiffOptions::Load(GetOptionsMgr(), options); diff --git a/Src/DirDoc.h b/Src/DirDoc.h index 41f15dcda..4dbe80ced 100644 --- a/Src/DirDoc.h +++ b/Src/DirDoc.h @@ -117,6 +117,7 @@ public: protected: void InitDiffContext(CDiffContext *pCtxt); void LoadLineFilterList(CDiffContext *pCtxt); + void LoadTokensForIgnoredSubstitutions(CDiffContext* pCtxt); // Generated message map functions //{{AFX_MSG(CDirDoc) diff --git a/Src/FilterList.cpp b/Src/FilterList.cpp index a4bf586bc..c93fe21d6 100644 --- a/Src/FilterList.cpp +++ b/Src/FilterList.cpp @@ -97,3 +97,164 @@ bool FilterList::Match(const std::string& string, int codepage/*=CP_UTF8*/) return retval; } +static std::string ExtractCommonPrefix(const std::string& str0, const std::string& str1) +{ + size_t strlen0 = str0.length(); + size_t strlen1 = str1.length(); + size_t minStrlen = strlen0 < strlen1 ? strlen0 : strlen1; + for (size_t c = 0; c < minStrlen; c++) + { + if (str0[c] != str1[c]) + return str0.substr(0, c); + } + return strlen0 < strlen1 ? str0 : str1; +} + +static std::string ExtractCommonSuffix(const std::string& str0, const std::string& str1) +{ + size_t strlen0 = str0.length(); + size_t strlen1 = str1.length(); + size_t minStrlen = strlen0 < strlen1 ? strlen0 : strlen1; + for (size_t c = 0; c < minStrlen; c++) + { + if (str0[strlen0 - 1 - c] != str1[strlen1 - 1 - c]) + return str0.substr(strlen0 - c, c); + } + return strlen0 < strlen1 ? str0 : str1; +} + +template +static std::string ExtractMiddleChangedPart(const std::string& str0, const std::string& str1) +{ + size_t strlen[2]{ str0.length(), str1.length() }; + size_t minStrlen = strlen[0] < strlen[1] ? strlen[0] : strlen[1]; + size_t diffStart = 0; + while (diffStart < minStrlen && str0[diffStart] == str1[diffStart]) + diffStart++; + size_t diffEnd = minStrlen - 1; + while (diffEnd < minStrlen && str0[strlen[0] - 1 - diffEnd] == str1[strlen[1] - 1 - diffEnd]) + diffEnd++; + + const std::string* strs[2]{ &str0, &str1 }; + return strs[index]->substr(diffStart, strlen[index] - diffEnd - 1); +} + +IgnoredSusbstitutionItem::IgnoredSusbstitutionItem +( + const std::string& filter0, const std::string& filter1, + int regexpCompileOptions, bool extractCommonSufixAndPrefix +) + : CommonPrefix(extractCommonSufixAndPrefix ? ExtractCommonPrefix(filter0, filter1) : "") + , CommonPrefixLength(CommonPrefix.length()) + , ChangedPart + { + extractCommonSufixAndPrefix ? ExtractMiddleChangedPart<0>(filter0, filter1) : filter0, + extractCommonSufixAndPrefix ? ExtractMiddleChangedPart<1>(filter0, filter1) : filter1 + } + , CommonSuffix(extractCommonSufixAndPrefix ? ExtractCommonSuffix(filter0, filter1) : "") + , CommonSuffixLength(CommonSuffix.length()) + , ChangedPartRegexp + { + Poco::RegularExpression(ChangedPart[0], regexpCompileOptions), + Poco::RegularExpression(ChangedPart[1], regexpCompileOptions) + } +{ +} + +IgnoredSubstitutionsFilterList::IgnoredSubstitutionsFilterList() +{ +} + +IgnoredSubstitutionsFilterList::~IgnoredSubstitutionsFilterList() +{ + RemoveAllFilters(); +} + +void IgnoredSubstitutionsFilterList::Add(const std::string& change0, const std::string& change1, bool extractCommonSufixAndPrefix) +{ + try + { + m_list.push_back + ( + std::shared_ptr( + new IgnoredSusbstitutionItem(change0, change1, RegularExpression::RE_UTF8, extractCommonSufixAndPrefix) + )); + } + catch (...) + { + // TODO: + } +} + +bool IgnoredSubstitutionsFilterList::MatchBoth +( + size_t filterIndex, + const std::string& string0, + const std::string& string1, + int codepage/*=CP_UTF8*/ +) const +{ + bool retval = false; + const size_t count = m_list.size(); + + if (filterIndex >= count) + return false; + + // convert string into UTF-8 + ucr::buffer buf0(string0.length() * 2); + ucr::buffer buf1(string1.length() * 2); + + if (codepage != ucr::CP_UTF_8) + { + ucr::convert(ucr::NONE, codepage, reinterpret_cast(string0.c_str()), + string0.length(), ucr::UTF8, ucr::CP_UTF_8, &buf0); + ucr::convert(ucr::NONE, codepage, reinterpret_cast(string1.c_str()), + string1.length(), ucr::UTF8, ucr::CP_UTF_8, &buf1); + } + + const IgnoredSusbstitutionItem &item = *m_list[filterIndex]; + int result = 0; + RegularExpression::Match match; + try + { + if (buf0.size > 0 && buf1.size > 0) + { + result = + item.ChangedPartRegexp[0].match(std::string(reinterpret_cast(buf0.ptr), buf0.size), 0, match) + && item.ChangedPartRegexp[1].match(std::string(reinterpret_cast(buf1.ptr), buf1.size), 0, match); + } + else + { + result = + item.ChangedPartRegexp[0].match(string0, 0, match) + && item.ChangedPartRegexp[1].match(string1, 0, match); + } + } + catch (...) + { + // TODO: + } + if (result > 0) + { + retval = true; + } + + return retval; +} + +void IgnoredSubstitutionsFilterList::RemoveAllFilters() +{ + m_list.clear(); +} + +bool IgnoredSubstitutionsFilterList::HasRegExps() const +{ + return !m_list.empty(); +} + +const IgnoredSusbstitutionItem &IgnoredSubstitutionsFilterList::operator[](int index) const +{ + return *m_list[index]; +} + + diff --git a/Src/FilterList.h b/Src/FilterList.h index ae8e2d1a3..ce8d0a43a 100644 --- a/Src/FilterList.h +++ b/Src/FilterList.h @@ -40,8 +40,10 @@ public: void AddRegExp(const std::string& regularExpression); void RemoveAllFilters(); bool HasRegExps() const; + size_t GetCount() const { return m_list.size(); } bool Match(const std::string& string, int codepage = ucr::CP_UTF_8); const char * GetLastMatchExpression() const; + const filter_item& operator[](int index) const; private: std::vector m_list; @@ -49,6 +51,45 @@ private: }; +struct IgnoredSusbstitutionItem +{ + std::string CommonPrefix; + size_t CommonPrefixLength; + std::string ChangedPart[2]; + std::string CommonSuffix; + size_t CommonSuffixLength; + Poco::RegularExpression ChangedPartRegexp[2]; /**< Compiled regular expression */ + + IgnoredSusbstitutionItem + ( + const std::string& filter0, const std::string& filter1, + int regexpCompileOptions, bool extractCommonSufixAndPrefix + ); +}; + +class IgnoredSubstitutionsFilterList +{ +public: + IgnoredSubstitutionsFilterList(); + ~IgnoredSubstitutionsFilterList(); + + void Add(const std::string& change0, const std::string& change1, bool extractCommonSufixAndPrefix); + void RemoveAllFilters(); + bool HasRegExps() const; + size_t GetCount() const { return m_list.size(); } + bool MatchBoth + ( + size_t filterIndex, + const std::string& string0, + const std::string& string1, + int codepage = CP_UTF8 + ) const; + const IgnoredSusbstitutionItem &operator[](int index) const; + +private: + std::vector> m_list; +}; + /** * @brief Removes all expressions from the list. */ @@ -75,3 +116,10 @@ inline const char * FilterList::GetLastMatchExpression() const { return m_lastMatchExpression->c_str(); } + +inline const filter_item& FilterList::operator[](int index) const +{ + const filter_item_ptr &item = m_list[index]; + return *item; +} + diff --git a/Src/FolderCmp.cpp b/Src/FolderCmp.cpp index 057732e47..0b1d37a29 100644 --- a/Src/FolderCmp.cpp +++ b/Src/FolderCmp.cpp @@ -263,6 +263,7 @@ int FolderCmp::prepAndCompareFiles(DIFFITEM &di) dw.SetCompareFiles(tFiles); dw.SetOptions(m_pCtxt->GetOptions()); dw.SetFilterList(m_pCtxt->m_pFilterList.get()); + dw.SetIgnoredSubstitutionsList(m_pCtxt->m_pTokenListsForIs[0].get(), m_pCtxt->m_pTokenListsForIs[1].get()); dw.SetFilterCommentsSourceDef(Ext); dw.SetCreateDiffList(&diffList); dw.LoadWinMergeDiffsFromDiffUtilsScript3( diff --git a/Src/IgnoredSubstitutionsDlg.cpp b/Src/IgnoredSubstitutionsDlg.cpp new file mode 100644 index 000000000..e6f64155f --- /dev/null +++ b/Src/IgnoredSubstitutionsDlg.cpp @@ -0,0 +1,203 @@ +/** + * @file IgnoredSubstitutionsFiltersDlg.cpp + * + * @brief Implementation of Line Filter dialog + */ + +#include "stdafx.h" +#include "TokenPairList.h" +#include "Merge.h" +#include "IgnoredSubstitutionsDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +/** @brief Location for file compare specific help to open. */ +static TCHAR FilterHelpLocation[] = _T("::/htmlhelp/Filters.html"); + +///////////////////////////////////////////////////////////////////////////// +// CPropLineFilter property page + +IMPLEMENT_DYNAMIC(IgnoredSubstitutionsDlg, CTrPropertyPage) + +/** + * @brief Constructor. + */ +IgnoredSubstitutionsDlg::IgnoredSubstitutionsDlg() + : CTrPropertyPage(IgnoredSubstitutionsDlg::IDD) + , m_pExternalRenameList(nullptr) + , InPlaceEdit(nullptr) +{ + //{{AFX_DATA_INIT(IgnoredSubstitutionsFiltersDlg) + m_IgnoredSubstitutionsAreEnabled = false; + m_IgnoredSubstitutionsWorkBothWays = false; + m_CompletelyBlankOutIgnoredSubstitutions = false; + m_UseRegexpsForIgnoredSubstitutions = false; + //}}AFX_DATA_INIT + m_strCaption = theApp.LoadDialogCaption(m_lpszTemplateName).c_str(); + m_psp.pszTitle = m_strCaption; + m_psp.dwFlags |= PSP_USETITLE; + m_psp.hIcon = AfxGetApp()->LoadIcon(IDI_LINEFILTER); + m_psp.dwFlags |= PSP_USEHICON; +} + +void IgnoredSubstitutionsDlg::DoDataExchange(CDataExchange* pDX) +{ + CPropertyPage::DoDataExchange(pDX); + //{{AFX_DATA_MAP(IgnoredSubstitutionsFiltersDlg) + DDX_Check(pDX, IDC_IGNORED_SUSBSTITUTIONS_ARE_ENABLED, m_IgnoredSubstitutionsAreEnabled); + DDX_Check(pDX, IDC_IGNORED_SUSBSTITUTIONS_WORK_BOTH_WAYS, m_IgnoredSubstitutionsWorkBothWays); + DDX_Check(pDX, IDC_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS, m_CompletelyBlankOutIgnoredSubstitutions); + DDX_Check(pDX, IDC_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS, m_UseRegexpsForIgnoredSubstitutions); + //}}AFX_DATA_MAP + DDX_Control(pDX, IDC_IGNORED_SUBSTITUTIONS_FILTER, m_VisibleFiltersList); +} + +BEGIN_MESSAGE_MAP(IgnoredSubstitutionsDlg, CTrPropertyPage) + //{{AFX_MSG_MAP(IgnoredSubstitutionsFiltersDlg) + ON_COMMAND(ID_HELP, OnHelp) + //}}AFX_MSG_MAP + ON_BN_CLICKED(IDC_LFILTER_ADDBTN, OnBnClickedAddBtn) + ON_BN_CLICKED(IDC_LFILTER_CLEARBTN, OnBnClickedClearBtn) + ON_BN_CLICKED(IDC_LFILTER_REMOVEBTN, OnBnClickedRemovebtn) + ON_NOTIFY(LVN_ENDLABELEDIT, IDC_IGNORED_SUBSTITUTIONS_FILTER, OnEndLabelEdit) +END_MESSAGE_MAP() + + +///////////////////////////////////////////////////////////////////////////// +// CPropLineFilter message handlers + +/** + * @brief Initialize the dialog. + */ +BOOL IgnoredSubstitutionsDlg::OnInitDialog() +{ + CTrPropertyPage::OnInitDialog(); + + InitList(); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + + +void IgnoredSubstitutionsDlg::InitList() +{ + m_VisibleFiltersList.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + //m_VisibleFiltersList.SetExtendedStyle(LVS_EX_INFOTIP | LVS_EX_GRIDLINES); + + const int lpx = CClientDC(this).GetDeviceCaps(LOGPIXELSX); + auto pointToPixel = [lpx](int point) { return MulDiv(point, lpx, 72); }; + + m_VisibleFiltersList.InsertColumn(0, _("On one panel").c_str(), LVCFMT_LEFT, pointToPixel(112)); + m_VisibleFiltersList.InsertColumn(1, _("On the other panel").c_str(), LVCFMT_LEFT, pointToPixel(262)); + + if (m_pExternalRenameList) + { + for (int i = 0; i < (int)m_pExternalRenameList->GetCount(); i++) + { + const TokenPair& item = m_pExternalRenameList->GetAt(i); + m_VisibleFiltersList.InsertItem(i, item.filterStr0.c_str()); + m_VisibleFiltersList.SetItemText(i, 1, item.filterStr1.c_str()); + } + } +} + +/** + * @brief Open help from mainframe when user presses F1. + */ +void IgnoredSubstitutionsDlg::OnHelp() +{ + theApp.ShowHelp(FilterHelpLocation); +} + +/** + * @brief Called when Add-button is clicked. + */ +void IgnoredSubstitutionsDlg::OnBnClickedAddBtn() +{ + int num = m_VisibleFiltersList.GetItemCount(); + int ind = m_VisibleFiltersList.InsertItem(num, _("").c_str()); + m_VisibleFiltersList.SetItemText(num, 1, _("").c_str()); + + if (ind >= -1) + { + m_VisibleFiltersList.SetItemState(ind, LVIS_SELECTED, LVIS_SELECTED); + m_VisibleFiltersList.EnsureVisible(ind, FALSE); + //EditSelectedFilter(); + } +} + +/** + * @brief Called when Clear-button is clicked. + */ +void IgnoredSubstitutionsDlg::OnBnClickedClearBtn() +{ + m_VisibleFiltersList.DeleteAllItems(); +} + +/** + * @brief Save filters to list when exiting the dialog. + */ +void IgnoredSubstitutionsDlg::OnOK() +{ + m_pExternalRenameList->Empty(); + + for (int i = 0; i < m_VisibleFiltersList.GetItemCount(); i++) + { + String symbolBeforeRename = m_VisibleFiltersList.GetItemText(i, 0); + String symbolAfterRename = m_VisibleFiltersList.GetItemText(i, 1); + if(symbolBeforeRename != _("") && symbolAfterRename != _("")) + m_pExternalRenameList->AddFilter(symbolBeforeRename, symbolAfterRename); + } + + CPropertyPage::OnClose(); + //CDialog::OnOK(); //? +} + +/** + * @brief Sets external filter list. + * @param [in] list External filter list. + */ +void IgnoredSubstitutionsDlg::SetList(TokenPairList *list) +{ + m_pExternalRenameList = list; +} + +/** + * @brief Called when Remove button is clicked. + */ +void IgnoredSubstitutionsDlg::OnBnClickedRemovebtn() +{ + int sel = m_VisibleFiltersList.GetNextItem(-1, LVNI_SELECTED); + if (sel != -1) + { + m_VisibleFiltersList.DeleteItem(sel); + } + + int newSel = min(m_VisibleFiltersList.GetItemCount() - 1, sel); + if (newSel >= -1) + { + m_VisibleFiltersList.SetItemState(newSel, LVIS_SELECTED, LVIS_SELECTED); + bool bPartialOk = false; + m_VisibleFiltersList.EnsureVisible(newSel, bPartialOk); + } +} + +/** + * @brief Called when the user activates an item. + */ +// void IgnoredSubstitutionsFiltersDlg::OnLvnItemActivate(NMHDR *pNMHDR, LRESULT *pResult) +// { +// EditSelectedFilter(); +// *pResult = 0; +// } + +/** + * @brief Called when in-place editing has finished. + */ +void IgnoredSubstitutionsDlg::OnEndLabelEdit(NMHDR *pNMHDR, LRESULT *pResult) +{ + m_VisibleFiltersList.OnEndLabelEdit(pNMHDR, pResult); +} diff --git a/Src/IgnoredSubstitutionsDlg.h b/Src/IgnoredSubstitutionsDlg.h new file mode 100644 index 000000000..b30c0cb4e --- /dev/null +++ b/Src/IgnoredSubstitutionsDlg.h @@ -0,0 +1,67 @@ +/** + * @file IgnoredSubstitutionsDlg.h + * + * @brief Declaration file for Line Filter dialog + * + */ +#pragma once + +#include "TrDialogs.h" +#include "SubeditList.h" + +class IgnoredSubstitutionFiltersList; + +class IgnoredSubstitutionsDlg : public CTrPropertyPage +{ + DECLARE_DYNAMIC(IgnoredSubstitutionsDlg) + +// Construction +public: + IgnoredSubstitutionsDlg(); + + void SetList(TokenPairList *list); + +// Dialog Data + //{{AFX_DATA(IgnoredSubstitutionsDlg) + enum { IDD = IDD_IGNORED_SUSBSTITUTIONS_DLG }; + bool m_IgnoredSubstitutionsAreEnabled; + bool m_IgnoredSubstitutionsWorkBothWays; + bool m_CompletelyBlankOutIgnoredSubstitutions; + bool m_UseRegexpsForIgnoredSubstitutions; + //}}AFX_DATA + +// Overrides + // ClassWizard generate virtual function overrides + //{{AFX_VIRTUAL(IgnoredSubstitutionsDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(IgnoredSubstitutionsDlg) + virtual BOOL OnInitDialog() override; + afx_msg void OnHelp(); + virtual void OnOK() override; + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnBnClickedAddBtn(); + afx_msg void OnBnClickedClearBtn(); + afx_msg void OnBnClickedEditbtn(); + afx_msg void OnBnClickedRemovebtn(); + afx_msg void OnLvnItemActivate(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnLvnKeyDown(NMHDR *pNMHDR, LRESULT *pResult); + afx_msg void OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() + + void InitList(); + +private: + std::unique_ptr InPlaceEdit; + + CSubeditList m_VisibleFiltersList; /**< List control having filter strings */ + + TokenPairList *m_pExternalRenameList; +}; diff --git a/Src/LineFiltersList.cpp b/Src/LineFiltersList.cpp index 9f0cf3873..f9c1a181b 100644 --- a/Src/LineFiltersList.cpp +++ b/Src/LineFiltersList.cpp @@ -14,7 +14,7 @@ using std::vector; /** @brief Registry key for saving linefilters. */ -static const TCHAR FiltersRegPath[] =_T("LineFilters"); +static const TCHAR LineFiltersRegPath[] = _T("LineFilters"); /** * @brief Default constructor. @@ -132,7 +132,7 @@ bool LineFiltersList::Compare(const LineFiltersList *list) const void LineFiltersList::Initialize(COptionsMgr *pOptionsMgr) { assert(pOptionsMgr != nullptr); - String valuename(FiltersRegPath); + String valuename(LineFiltersRegPath); m_pOptionsMgr = pOptionsMgr; @@ -143,11 +143,11 @@ void LineFiltersList::Initialize(COptionsMgr *pOptionsMgr) for (unsigned i = 0; i < count; i++) { - String name = strutils::format(_T("%s/Filter%02u"), FiltersRegPath, i); + String name = strutils::format(_T("%s/Filter%02u"), LineFiltersRegPath, i); m_pOptionsMgr->InitOption(name, _T("")); String filter = m_pOptionsMgr->GetString(name); - name = strutils::format(_T("%s/Enabled%02u"), FiltersRegPath, i); + name = strutils::format(_T("%s/Enabled%02u"), LineFiltersRegPath, i); m_pOptionsMgr->InitOption(name, (int)true); int enabled = m_pOptionsMgr->GetInt(name); bool bEnabled = enabled ? true : false; @@ -162,7 +162,7 @@ void LineFiltersList::Initialize(COptionsMgr *pOptionsMgr) void LineFiltersList::SaveFilters() { assert(m_pOptionsMgr != nullptr); - String valuename(FiltersRegPath); + String valuename(LineFiltersRegPath); size_t count = m_items.size(); valuename += _T("/Values"); @@ -172,29 +172,29 @@ void LineFiltersList::SaveFilters() { const LineFilterItemPtr& item = m_items[i]; - String name = strutils::format(_T("%s/Filter%02u"), FiltersRegPath, i); + String name = strutils::format(_T("%s/Filter%02u"), LineFiltersRegPath, i); m_pOptionsMgr->InitOption(name, _T("")); m_pOptionsMgr->SaveOption(name, item->filterStr); - name = strutils::format(_T("%s/Enabled%02u"), FiltersRegPath, i); + name = strutils::format(_T("%s/Enabled%02u"), LineFiltersRegPath, i); m_pOptionsMgr->InitOption(name, 0); m_pOptionsMgr->SaveOption(name, (int)item->enabled); } // Remove options we don't need anymore // We could have earlier 10 pcs but now we only need 5 - String filter = strutils::format(_T("%s/Filter%02u"), FiltersRegPath, count); + String filter = strutils::format(_T("%s/Filter%02u"), LineFiltersRegPath, count); int retval1 = m_pOptionsMgr->RemoveOption(filter); - filter = strutils::format(_T("%s/Enabled%02u"), FiltersRegPath, count); + filter = strutils::format(_T("%s/Enabled%02u"), LineFiltersRegPath, count); int retval2 = m_pOptionsMgr->RemoveOption(filter); while (retval1 == COption::OPT_OK || retval2 == COption::OPT_OK) { ++count; - filter = strutils::format(_T("%s/Filter%02u"), FiltersRegPath, count); + filter = strutils::format(_T("%s/Filter%02u"), LineFiltersRegPath, count); retval1 = m_pOptionsMgr->RemoveOption(filter); - filter = strutils::format(_T("%s/Enabled%02u"), FiltersRegPath, count); + filter = strutils::format(_T("%s/Enabled%02u"), LineFiltersRegPath, count); retval2 = m_pOptionsMgr->RemoveOption(filter); } } diff --git a/Src/MainFrm.cpp b/Src/MainFrm.cpp index c41c8380b..3e64de509 100644 --- a/Src/MainFrm.cpp +++ b/Src/MainFrm.cpp @@ -33,8 +33,10 @@ #include "HexMergeView.h" #include "ImgMergeFrm.h" #include "LineFiltersList.h" +#include "TokenPairList.h" #include "ConflictFileParser.h" #include "LineFiltersDlg.h" +#include "IgnoredSubstitutionsDlg.h" #include "paths.h" #include "Environment.h" #include "PatchTool.h" @@ -1627,12 +1629,15 @@ void CMainFrame::OnToolsFilters() String title = _("Filters"); CPropertySheet sht(title.c_str()); LineFiltersDlg lineFiltersDlg; + IgnoredSubstitutionsDlg ignoredSubstitutionsFiltersDlg; FileFiltersDlg fileFiltersDlg; std::unique_ptr lineFilters(new LineFiltersList()); + std::unique_ptr ignoredSubstitutionsFilters(new TokenPairList()); String selectedFilter; const String origFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask(); sht.AddPage(&fileFiltersDlg); sht.AddPage(&lineFiltersDlg); + sht.AddPage(&ignoredSubstitutionsFiltersDlg); sht.m_psh.dwFlags |= PSH_NOAPPLYNOW; // Hide 'Apply' button since we don't need it // Make sure all filters are up-to-date @@ -1646,6 +1651,19 @@ void CMainFrame::OnToolsFilters() lineFilters->CloneFrom(theApp.m_pLineFilters.get()); lineFiltersDlg.SetList(lineFilters.get()); + const bool ignoredSubstitutionsAreEnabledOrig = GetOptionsMgr()->GetBool(OPT_IGNORED_SUBSTITUTIONS_ARE_ENABLED); + const bool ignoredSubstitutionsWorkBothWaysOrig = GetOptionsMgr()->GetBool(OPT_IGNORED_SUBSTITUTIONS_WORK_BOTH_WAYS); + const bool completelyBlankOutIgnoredSubstitutionsOrig = GetOptionsMgr()->GetBool(OPT_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS); + const bool optUseRegexpsForSubstitutionsOrig = GetOptionsMgr()->GetBool(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS); + + ignoredSubstitutionsFiltersDlg.m_IgnoredSubstitutionsAreEnabled = ignoredSubstitutionsAreEnabledOrig; + ignoredSubstitutionsFiltersDlg.m_IgnoredSubstitutionsWorkBothWays = ignoredSubstitutionsWorkBothWaysOrig; + ignoredSubstitutionsFiltersDlg.m_CompletelyBlankOutIgnoredSubstitutions = completelyBlankOutIgnoredSubstitutionsOrig; + ignoredSubstitutionsFiltersDlg.m_UseRegexpsForIgnoredSubstitutions = optUseRegexpsForSubstitutionsOrig; + + ignoredSubstitutionsFilters->CloneFrom(theApp.m_pTokensForIs.get()); + ignoredSubstitutionsFiltersDlg.SetList(ignoredSubstitutionsFilters.get()); + if (sht.DoModal() == IDOK) { String strNone = _(""); @@ -1670,6 +1688,19 @@ void CMainFrame::OnToolsFilters() bool linefiltersEnabled = lineFiltersDlg.m_bIgnoreRegExp; GetOptionsMgr()->SaveOption(OPT_LINEFILTER_ENABLED, linefiltersEnabled); + bool ignoredSubstitutionsAreEnabled = ignoredSubstitutionsFiltersDlg.m_IgnoredSubstitutionsAreEnabled; + GetOptionsMgr()->SaveOption(OPT_IGNORED_SUBSTITUTIONS_ARE_ENABLED, ignoredSubstitutionsAreEnabled); + + bool ignoredSubstitutionsWorkBothWays = ignoredSubstitutionsFiltersDlg.m_IgnoredSubstitutionsWorkBothWays; + GetOptionsMgr()->SaveOption(OPT_IGNORED_SUBSTITUTIONS_WORK_BOTH_WAYS, ignoredSubstitutionsWorkBothWays); + + bool completelyBlankOutIgnoredSubstitutions = ignoredSubstitutionsFiltersDlg.m_CompletelyBlankOutIgnoredSubstitutions; + GetOptionsMgr()->SaveOption(OPT_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS, completelyBlankOutIgnoredSubstitutions); + + bool optUseRegexpsForSubstitutions = GetOptionsMgr()->GetBool(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS); + GetOptionsMgr()->SaveOption(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS, optUseRegexpsForSubstitutions); + + // Check if compare documents need rescanning bool bFileCompareRescan = false; bool bFolderCompareRescan = false; @@ -1677,8 +1708,16 @@ void CMainFrame::OnToolsFilters() FRAMETYPE frame = GetFrameType(pFrame); if (frame == FRAME_FILE) { - if (lineFiltersEnabledOrig != linefiltersEnabled || - !theApp.m_pLineFilters->Compare(lineFilters.get())) + if + ( + linefiltersEnabled != lineFiltersEnabledOrig + || ignoredSubstitutionsAreEnabled != ignoredSubstitutionsAreEnabledOrig + || ignoredSubstitutionsWorkBothWays != ignoredSubstitutionsWorkBothWaysOrig + || completelyBlankOutIgnoredSubstitutions != completelyBlankOutIgnoredSubstitutionsOrig + || optUseRegexpsForSubstitutions != optUseRegexpsForSubstitutionsOrig + || !lineFilters->Compare(theApp.m_pLineFilters.get()) + || !ignoredSubstitutionsFilters->Compare(theApp.m_pTokensForIs.get()) + ) { bFileCompareRescan = true; } @@ -1699,6 +1738,10 @@ void CMainFrame::OnToolsFilters() theApp.m_pLineFilters->CloneFrom(lineFilters.get()); theApp.m_pLineFilters->SaveFilters(); + theApp.m_pTokensForIs->CloneFrom(ignoredSubstitutionsFilters.get()); + theApp.m_pTokensForIs->SaveFilters(); + + if (bFileCompareRescan) { for (auto pMergeDoc : GetAllMergeDocs()) diff --git a/Src/Merge.cpp b/Src/Merge.cpp index 5b6e7adff..9d44b6759 100644 --- a/Src/Merge.cpp +++ b/Src/Merge.cpp @@ -38,6 +38,7 @@ #include "paths.h" #include "FileFilterHelper.h" #include "LineFiltersList.h" +#include "TokenPairList.h" #include "SyntaxColors.h" #include "CCrystalTextMarkers.h" #include "OptionsSyntaxColors.h" @@ -105,6 +106,7 @@ CMergeApp::CMergeApp() : , m_bEscShutdown(false) , m_bExitIfNoDiff(MergeCmdLineInfo::Disabled) , m_pLineFilters(new LineFiltersList()) +, m_pTokensForIs(new TokenPairList()) , m_pSyntaxColors(new SyntaxColors()) , m_pMarkers(new CCrystalTextMarkers()) , m_bMergingMode(false) @@ -307,6 +309,9 @@ BOOL CMergeApp::InitInstance() m_pLineFilters->Import(oldFilter); } + if (m_pTokensForIs != nullptr) + m_pTokensForIs->Initialize(GetOptionsMgr()); + // Check if filter folder is set, and create it if not String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH); if (pathMyFolders.empty()) diff --git a/Src/Merge.h b/Src/Merge.h index 70402163a..0b9d7eb12 100644 --- a/Src/Merge.h +++ b/Src/Merge.h @@ -34,6 +34,7 @@ class MergeCmdLineInfo; class ProjectFile; class COptionsMgr; class LineFiltersList; +class TokenPairList; class SyntaxColors; class CCrystalTextMarkers; @@ -65,6 +66,7 @@ public: CCrystalTextMarkers * GetMainMarkers() const { return m_pMarkers.get(); } MergeCmdLineInfo::ExitNoDiff m_bExitIfNoDiff; /**< Exit if files are identical? */ std::unique_ptr m_pLineFilters; /**< List of linefilters */ + std::unique_ptr m_pTokensForIs; WORD GetLangId() const; void SetIndicators(CStatusBar &, const UINT *, int) const; diff --git a/Src/Merge.rc b/Src/Merge.rc index dfee303e5..bf33a575a 100644 --- a/Src/Merge.rc +++ b/Src/Merge.rc @@ -72,6 +72,7 @@ BEGIN MENUITEM "Copy fro&m Right", ID_COPY_FROM_RIGHT MENUITEM SEPARATOR MENUITEM "&Select Line Difference\tF4", ID_SELECTLINEDIFF + MENUITEM "Add this change to &Ignored Substitutions", ID_ADD_TO_IGNORED_SUBSTITUTIONS MENUITEM SEPARATOR MENUITEM "&Undo", ID_EDIT_UNDO MENUITEM "&Redo", ID_EDIT_REDO @@ -1165,7 +1166,7 @@ END IDD_PROPPAGE_FILTER DIALOGEX 0, 0, 535, 188 STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_DISABLED | WS_CAPTION -CAPTION "Linefilters" +CAPTION "Line Filters" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN CONTROL "Enable Line Filters",IDC_IGNOREREGEXP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,351,15 @@ -1176,6 +1177,25 @@ BEGIN PUSHBUTTON "Remove",IDC_LFILTER_REMOVEBTN,116,167,50,14 END +IDD_IGNORED_SUSBSTITUTIONS_DLG DIALOGEX 0, 0, 365, 281 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_DISABLED | WS_CAPTION +CAPTION "Ignored Substitutions" +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + CONTROL "Enable",IDC_IGNORED_SUSBSTITUTIONS_ARE_ENABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,28,351,9 + CONTROL "",IDC_IGNORED_SUBSTITUTIONS_FILTER,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_EDITLABELS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,8,87,350,166 + PUSHBUTTON "Add",IDC_LFILTER_ADDBTN,7,260,50,14 + PUSHBUTTON "Remove",IDC_LFILTER_REMOVEBTN,63,260,50,14 + LTEXT "The changes that appear on the panels as the listed pairs below will be ignored or marked as insignificant. Patches are unaffected.",IDC_STATIC,8,6,346,19 + CONTROL "Ignore changes in both directions",IDC_IGNORED_SUSBSTITUTIONS_WORK_BOTH_WAYS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,57,351,10 + CONTROL "Completely unhighlight the ignored changes",IDC_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,43,341,10 + CONTROL "Use regular expressions",IDC_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,72,350,10 + PUSHBUTTON "Clear",IDC_LFILTER_CLEARBTN,307,261,50,14 +END + IDD_PROPPAGE_COLOR_SCHEMES DIALOGEX 0, 0, 255, 242 STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION CAPTION "Colors" @@ -1451,7 +1471,7 @@ END IDD_FILEFILTERS DIALOGEX 0, 0, 537, 188 STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION -CAPTION "Filefilters" +CAPTION "File Filters" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN CONTROL "",IDC_FILTERFILE_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,10,7,517,153 @@ -1991,6 +2011,11 @@ BEGIN BEGIN END + IDD_IGNORED_SUSBSTITUTIONS_DLG, DIALOG + BEGIN + BOTTOMMARGIN, 196 + END + IDD_PROPPAGE_COLOR_SCHEMES, DIALOG BEGIN END diff --git a/Src/Merge.vs2017.vcxproj.filters b/Src/Merge.vs2017.vcxproj.filters index 61cbfdaf7..6273b8ac1 100644 --- a/Src/Merge.vs2017.vcxproj.filters +++ b/Src/Merge.vs2017.vcxproj.filters @@ -523,6 +523,9 @@ MFCGui\Dialogs\Source Files + + MFCGui\Dialogs\Source Files + MFCGui\Dialogs\Source Files @@ -916,6 +919,12 @@ EditLib\Utils + + MFCGui\Dialogs\Source Files + + + Source Files + EditLib\Parsers\Source Files @@ -1029,9 +1038,6 @@ Header Files - - Header Files - Header Files @@ -1653,6 +1659,15 @@ EditLib\Utils + + Header Files + + + MFCGui\Dialogs\Header Files + + + Header Files + diff --git a/Src/Merge.vs2019.vcxproj b/Src/Merge.vs2019.vcxproj index 07b195a57..591c64589 100644 --- a/Src/Merge.vs2019.vcxproj +++ b/Src/Merge.vs2019.vcxproj @@ -753,11 +753,18 @@ $(IntDir)$(TargetName)2.pch + + Use pch.h $(IntDir)$(TargetName)2.pch + + Use + pch.h + $(IntDir)$(TargetName)2.pch + Use @@ -1199,7 +1206,9 @@ + + diff --git a/Src/Merge.vs2019.vcxproj.filters b/Src/Merge.vs2019.vcxproj.filters index 61cbfdaf7..6273b8ac1 100644 --- a/Src/Merge.vs2019.vcxproj.filters +++ b/Src/Merge.vs2019.vcxproj.filters @@ -523,6 +523,9 @@ MFCGui\Dialogs\Source Files + + MFCGui\Dialogs\Source Files + MFCGui\Dialogs\Source Files @@ -916,6 +919,12 @@ EditLib\Utils + + MFCGui\Dialogs\Source Files + + + Source Files + EditLib\Parsers\Source Files @@ -1029,9 +1038,6 @@ Header Files - - Header Files - Header Files @@ -1653,6 +1659,15 @@ EditLib\Utils + + Header Files + + + MFCGui\Dialogs\Header Files + + + Header Files + diff --git a/Src/MergeDoc.cpp b/Src/MergeDoc.cpp index 85fd14286..d7948abab 100644 --- a/Src/MergeDoc.cpp +++ b/Src/MergeDoc.cpp @@ -39,6 +39,7 @@ #include "MergeLineFlags.h" #include "FileOrFolderSelect.h" #include "LineFiltersList.h" +#include "TokenPairList.h" #include "TempFile.h" #include "codepage_detect.h" #include "SelectUnpackerDlg.h" @@ -301,6 +302,16 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical, { m_diffWrapper.SetFilterList(_T("")); } + + if (GetOptionsMgr()->GetBool(OPT_IGNORED_SUBSTITUTIONS_ARE_ENABLED) && theApp.m_pTokensForIs) + { + m_diffWrapper.SetIgnoredSubstitutionsList(theApp.m_pTokensForIs.get()); + } + else + { + m_diffWrapper.SetIgnoredSubstitutionsList(nullptr); + } + if (GetView(0, 0)->m_CurSourceDef->type != 0) m_diffWrapper.SetFilterCommentsSourceDef(GetView(0, 0)->m_CurSourceDef); else diff --git a/Src/MergeDoc.h b/Src/MergeDoc.h index b2e800bcf..228cad079 100644 --- a/Src/MergeDoc.h +++ b/Src/MergeDoc.h @@ -258,6 +258,7 @@ public: public: typedef enum { BYTEDIFF, WORDDIFF } DIFFLEVEL; void Showlinediff(CMergeEditView *pView, bool bReversed = false); + void AddToIgnoredSubstitutions(CMergeEditView* pView, bool bReversed = false); std::vector GetWordDiffArrayInDiffBlock(int nDiff); std::vector GetWordDiffArray(int nLineIndex); void ClearWordDiffCache(int nDiff = -1); diff --git a/Src/MergeDocLineDiffs.cpp b/Src/MergeDocLineDiffs.cpp index 237d5221b..f033b9379 100644 --- a/Src/MergeDocLineDiffs.cpp +++ b/Src/MergeDocLineDiffs.cpp @@ -13,6 +13,10 @@ #include "DiffTextBuffer.h" #include "stringdiffs.h" #include "UnicodeString.h" +#include "TokenPairList.h" +#include "OptionsMgr.h" +#include "OptionsDef.h" +#include "Merge.h" #ifdef _DEBUG #define new DEBUG_NEW @@ -49,22 +53,87 @@ HighlightDiffRect(CMergeEditView * pView, const CRect & rc) void CMergeDoc::Showlinediff(CMergeEditView *pView, bool bReversed) { CRect rc[3]; - int nBuffer; Computelinediff(pView, rc, bReversed); if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.top == -1; })) { String caption = _("Line difference"); - String msg = _("No difference"); + String msg = _("No differences to select found"); MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_OK); return; } // Actually display selection areas on screen in both edit panels - for (int nGroup = 0; nGroup < m_nGroups; nGroup++) - for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++) - HighlightDiffRect(m_pView[nGroup][nBuffer], rc[nBuffer]); + for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++) + HighlightDiffRect(m_pView[pView->m_nThisGroup][nBuffer], rc[nBuffer]); +} + +void CMergeDoc::AddToIgnoredSubstitutions(CMergeEditView* pView, bool bReversed) +{ + if (m_nBuffers != 2) + return; /// Not clear what to do for a 3-way merge + + CRect rc[3]; + + Computelinediff(pView, rc, bReversed); + + bool optIgnoredSubstitutionsWorkBothWays = GetOptionsMgr()->GetBool(OPT_IGNORED_SUBSTITUTIONS_WORK_BOTH_WAYS); + + if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.top == -1; })) + { + String caption = _("Line difference"); + String msg = _("No differences found to add as ignored substitution"); + MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_OK); + return; + } + + // Actually display selection areas on screen in both edit panels + String selectedText[3]; + for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++) + { + HighlightDiffRect(m_pView[pView->m_nThisGroup][nBuffer], rc[nBuffer]); + selectedText[nBuffer] = String(m_pView[pView->m_nThisGroup][nBuffer]->GetSelectedText()); + } + + if (selectedText[0].empty() && selectedText[1].empty()) + { + return; + } + + + /// Check whether the pair is already registered with Ignored Substitutions + TokenPairList &ignoredSubstitutionsList = *theApp.m_pTokensForIs.get(); + for (int f = 0; f < ignoredSubstitutionsList.GetCount(); f++) + { + String str0 = ignoredSubstitutionsList.GetAt(f).filterStr0; + String str1 = ignoredSubstitutionsList.GetAt(f).filterStr1; + if + ( + str0 == selectedText[0] + && str1 == selectedText[1] + || + optIgnoredSubstitutionsWorkBothWays + && str1 == selectedText[0] + && str0 == selectedText[1] + ) + { + String caption = _("The pair is already present in the list of Ignored Substiturions"); + String msg = strutils::format(_T("\"%s\" <-> \"%s\""), selectedText[0], selectedText[1]); + MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_OK); + return; /// The substitution pair is already registered + } + } + + String caption = _("Add this change to Ignored Substitutions?"); + String msg = strutils::format(_T("\"%s\" <-> \"%s\""), selectedText[0], selectedText[1]); + if (MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_YESNO) == IDYES) + { + ignoredSubstitutionsList.AddFilter(selectedText[0], selectedText[1]); + FlushAndRescan(true); + //Rescan(); + } + return; } static inline bool IsDiffPerLine(bool bTableEditing, const DIFFRANGE& cd) diff --git a/Src/MergeEditView.cpp b/Src/MergeEditView.cpp index 73efd3b20..23a3564bc 100644 --- a/Src/MergeEditView.cpp +++ b/Src/MergeEditView.cpp @@ -159,6 +159,8 @@ BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx) ON_UPDATE_COMMAND_UI(ID_SELECTLINEDIFF, OnUpdateSelectLineDiff) ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff) ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff) + ON_COMMAND(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnAddToIgnoredSubstitutions) + ON_UPDATE_COMMAND_UI(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnUpdateAddToIgnoredSubstitutions) ON_WM_CONTEXTMENU() ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace) ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly) @@ -2696,6 +2698,19 @@ void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI) pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan()); } +template +void CMergeEditView::OnAddToIgnoredSubstitutions() +{ + // Pass this to the document, to compare this file to other + GetDocument()->AddToIgnoredSubstitutions(this, reversed); +} + +void CMergeEditView::OnUpdateAddToIgnoredSubstitutions(CCmdUI* pCmdUI) +{ + pCmdUI->Enable(GetDocument()->m_nBuffers == 2 && !GetDocument()->IsEditedAfterRescan()); +} + + /** * @brief Enable/disable Replace-menuitem */ diff --git a/Src/MergeEditView.h b/Src/MergeEditView.h index 192560b1b..67b11f50e 100644 --- a/Src/MergeEditView.h +++ b/Src/MergeEditView.h @@ -283,6 +283,9 @@ protected: template afx_msg void OnSelectLineDiff(); afx_msg void OnUpdateSelectLineDiff(CCmdUI* pCmdUI); + template + afx_msg void OnAddToIgnoredSubstitutions(); + afx_msg void OnUpdateAddToIgnoredSubstitutions(CCmdUI* pCmdUI); afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); afx_msg void OnUpdateEditReplace(CCmdUI* pCmdUI); afx_msg void OnLeftReadOnly(); diff --git a/Src/OptionsDef.h b/Src/OptionsDef.h index 72652d654..a9ec05cb7 100644 --- a/Src/OptionsDef.h +++ b/Src/OptionsDef.h @@ -248,6 +248,12 @@ extern const String OPT_FILEFILTER_CURRENT OP("Settings/FileFilterCurrent"); extern const String OPT_FILTER_USERPATH OP("Settings/UserFilterPath"); extern const String OPT_FILEFILTER_SHARED OP("Settings/Filters/Shared"); +/// Ignored Susbstitutions +extern const String OPT_IGNORED_SUBSTITUTIONS_ARE_ENABLED OP("Settings/IgnoredSubstitutionsAreEnabled"); +extern const String OPT_IGNORED_SUBSTITUTIONS_WORK_BOTH_WAYS OP("Settings/IgnoredSubstitutionsWorkBothWays"); +extern const String OPT_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS OP("Settings/CompletelyBlankOutIgnoredSusbstitutions"); +extern const String OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS OP("Settings/UseRegexpsForIgnoredSubstitutions"); + // Archive support extern const String OPT_ARCHIVE_ENABLE OP("Merge7z/Enable"); extern const String OPT_ARCHIVE_PROBETYPE OP("Merge7z/ProbeSignature"); diff --git a/Src/OptionsInit.cpp b/Src/OptionsInit.cpp index e4f7adc92..8e0c7574e 100644 --- a/Src/OptionsInit.cpp +++ b/Src/OptionsInit.cpp @@ -165,6 +165,11 @@ void Init(COptionsMgr *pOptions) pOptions->InitOption(OPT_CUSTOM_TEMP_PATH, _T("")); pOptions->InitOption(OPT_LINEFILTER_ENABLED, false); + pOptions->InitOption(OPT_IGNORED_SUBSTITUTIONS_ARE_ENABLED, false); + pOptions->InitOption(OPT_IGNORED_SUBSTITUTIONS_WORK_BOTH_WAYS, false); + pOptions->InitOption(OPT_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS, false); + pOptions->InitOption(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS, false); + pOptions->InitOption(OPT_FILEFILTER_CURRENT, _T("*.*")); // CMainFrame initializes this when it is empty. pOptions->InitOption(OPT_FILTER_USERPATH, _T("")); diff --git a/Src/SubeditList.cpp b/Src/SubeditList.cpp new file mode 100644 index 000000000..af6aa2711 --- /dev/null +++ b/Src/SubeditList.cpp @@ -0,0 +1,353 @@ +// SubeditList.cpp : implementation file +// + +#include "stdafx.h" +#include "subedit.h" +#include "SubeditList.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define IDC_IPEDIT 1000 + +/// Some stuff is from https://www.codeguru.com/cpp/controls/listview/editingitemsandsubitem/article.php/c923/Editable-subitems.htm + +///////////////////////////////////////////////////////////////////////////// +// CSubeditList + +CSubeditList::CSubeditList() +{ +} + +CSubeditList::~CSubeditList() +{ +} + +// HitTestEx - Determine the row index and column index for a point +// Returns - the row index or -1 if point is not over a row +// point - point to be tested. +// col - to hold the column index +int CSubeditList::HitTestEx(CPoint &point, int *col) const +{ + int colnum = 0; + int row = HitTest( point, NULL ); + + if( col ) *col = 0; + + // Make sure that the ListView is in LVS_REPORT + if( (GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK) != LVS_REPORT ) + return row; + + // Get the top and bottom row visible + row = GetTopIndex(); + int bottom = row + GetCountPerPage(); + if( bottom > GetItemCount() ) + bottom = GetItemCount(); + + // Get the number of columns + CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); + int nColumnCount = pHeader->GetItemCount(); + + // Loop through the visible rows + for( ;row <=bottom;row++) + { + // Get bounding rect of item and check whether point falls in it. + CRect rect; + GetItemRect( row, &rect, LVIR_BOUNDS ); + if( rect.PtInRect(point) ) + { + // Now find the column + for( colnum = 0; colnum < nColumnCount; colnum++ ) + { + int colwidth = GetColumnWidth(colnum); + if( point.x >= rect.left + && point.x <= (rect.left + colwidth ) ) + { + if( col ) *col = colnum; + return row; + } + rect.left += colwidth; + } + } + } + return -1; +} + + +BEGIN_MESSAGE_MAP(CSubeditList, CListCtrl) + //{{AFX_MSG_MAP(CSubeditList) + // NOTE - the ClassWizard will add and remove mapping macros here. + ON_NOTIFY(LVN_ENDLABELEDIT, IDC_IGNORED_SUBSTITUTIONS_FILTER, OnEndLabelEdit) + ON_WM_LBUTTONDOWN() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSubeditList message handlers + + +// EditSubLabel - Start edit of a sub item label +// Returns - Temporary pointer to the new edit control +// nItem - The row index of the item to edit +// nCol - The column of the sub item. +//CEdit* CSubeditList::EditSubLabel(int nItem, int nCol) +CInPlaceEdit* CSubeditList::EditSubLabel( int nItem, int nCol ) +{ + // The returned pointer should not be saved + + // Make sure that the item is visible + if( !EnsureVisible( nItem, TRUE ) ) return NULL; + + // Make sure that nCol is valid + CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); + int nColumnCount = pHeader->GetItemCount(); + if( nCol >= nColumnCount || GetColumnWidth(nCol) < 5 ) + return NULL; + + // Get the column offset + int offset = 0; + for( int i = 0; i < nCol; i++ ) + offset += GetColumnWidth( i ); + + CRect rect; + GetItemRect( nItem, &rect, LVIR_BOUNDS ); + + // Now scroll if we need to expose the column + CRect rcClient; + GetClientRect( &rcClient ); + if( offset + rect.left < 0 || offset + rect.left > rcClient.right ) + { + CSize size; + size.cx = offset + rect.left; + size.cy = 0; + Scroll( size ); + rect.left -= size.cx; + } + + // Get Column alignment + LV_COLUMN lvcol; + lvcol.mask = LVCF_FMT; + GetColumn( nCol, &lvcol ); + DWORD dwStyle ; + if((lvcol.fmt&LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT) + dwStyle = ES_LEFT; + else if((lvcol.fmt&LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT) + dwStyle = ES_RIGHT; + else dwStyle = ES_CENTER; + + rect.left += offset+4; + rect.right = rect.left + GetColumnWidth( nCol ) - 3 ; + if( rect.right > rcClient.right) rect.right = rcClient.right; + + dwStyle |= WS_BORDER|WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL; + CInPlaceEdit *pEdit = new CInPlaceEdit(nItem, nCol, GetItemText( nItem, nCol )); + pEdit->Create( dwStyle, rect, this, IDC_IPEDIT ); + + return pEdit; +} + +void CSubeditList::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) +{ + if( GetFocus() != this ) SetFocus(); + CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar); +} + +void CSubeditList::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) +{ + if( GetFocus() != this ) SetFocus(); + CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar); +} + +void CSubeditList::OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult) +{ + LV_DISPINFO *plvDispInfo = (LV_DISPINFO *)pNMHDR; + LV_ITEM *plvItem = &plvDispInfo->item; + + if (plvItem->pszText != NULL) + { + SetItemText(plvItem->iItem, plvItem->iSubItem, plvItem->pszText); + } + *pResult = FALSE; +} + +void CSubeditList::OnBeginLabelEdit(NMHDR* pNMHDR, LRESULT* pResult) +{ + LV_DISPINFO* plvDispInfo = (LV_DISPINFO*)pNMHDR; + LV_ITEM* plvItem = &plvDispInfo->item; + plvItem->iSubItem = 1; + +// if (plvItem->pszText != NULL) +// { +// SetItemText(plvItem->iItem, plvItem->iSubItem, plvItem->pszText); +// } + *pResult = FALSE; +} + +void CSubeditList::OnLButtonDown(UINT nFlags, CPoint point) +{ + int index; + CListCtrl::OnLButtonDown(nFlags, point); + + int colnum; + if( ( index = HitTestEx( point, &colnum )) != -1 ) + { + UINT flag = LVIS_FOCUSED; + //if ((GetItemState(index, flag) & flag) == flag && colnum > 0) + if ((GetItemState(index, flag) & flag) == flag) + { + // Add check for LVS_EDITLABELS + if( GetWindowLong(m_hWnd, GWL_STYLE) & LVS_EDITLABELS ) + EditSubLabel( index, colnum ); + } + else + SetItemState( index, LVIS_SELECTED | LVIS_FOCUSED , + LVIS_SELECTED | LVIS_FOCUSED); + } +} + +///////////////////////////////////////////////////////////////////////////// +// CInPlaceEdit + +CInPlaceEdit::CInPlaceEdit(int iItem, int iSubItem, CString sInitText) +:m_sInitText( sInitText ) +{ + m_iItem = iItem; + m_iSubItem = iSubItem; + m_bESC = FALSE; +} + +CInPlaceEdit::~CInPlaceEdit() +{ +} + + +BEGIN_MESSAGE_MAP(CInPlaceEdit, CEdit) + //{{AFX_MSG_MAP(CInPlaceEdit) + ON_WM_KILLFOCUS() + ON_WM_NCDESTROY() + ON_WM_CHAR() + ON_WM_CREATE() + //}}AFX_MSG_MAP + ON_WM_LBUTTONDOWN() +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CInPlaceEdit message handlers + +BOOL CInPlaceEdit::PreTranslateMessage(MSG* pMsg) +{ + if( pMsg->message == WM_KEYDOWN ) + { + if(pMsg->wParam == VK_RETURN + || pMsg->wParam == VK_DELETE + || pMsg->wParam == VK_ESCAPE + || GetKeyState( VK_CONTROL) + ) + { + ::TranslateMessage(pMsg); + ::DispatchMessage(pMsg); + return TRUE; // DO NOT process further + } + } + + return CEdit::PreTranslateMessage(pMsg); +} + + +void CInPlaceEdit::OnKillFocus(CWnd* pNewWnd) +{ + CEdit::OnKillFocus(pNewWnd); + + CString str; + GetWindowText(str); + + // Send Notification to parent of ListView ctrl + LV_DISPINFO dispinfo; + dispinfo.hdr.hwndFrom = GetParent()->m_hWnd; + dispinfo.hdr.idFrom = GetDlgCtrlID(); + dispinfo.hdr.code = LVN_ENDLABELEDIT; + + dispinfo.item.mask = LVIF_TEXT; + dispinfo.item.iItem = m_iItem; + dispinfo.item.iSubItem = m_iSubItem; + dispinfo.item.pszText = m_bESC ? NULL : LPTSTR((LPCTSTR)str); + dispinfo.item.cchTextMax = str.GetLength(); + + GetParent()->GetParent()->SendMessage( WM_NOTIFY, GetParent()->GetDlgCtrlID(), + (LPARAM)&dispinfo ); + + DestroyWindow(); +} + +void CInPlaceEdit::OnNcDestroy() +{ + CEdit::OnNcDestroy(); + + delete this; +} + + +void CInPlaceEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) +{ + if( nChar == VK_ESCAPE || nChar == VK_RETURN) + { + if( nChar == VK_ESCAPE ) + m_bESC = TRUE; + GetParent()->SetFocus(); + return; + } + + + CEdit::OnChar(nChar, nRepCnt, nFlags); + + // Resize edit control if needed + + // Get text extent + CString str; + + GetWindowText( str ); + CWindowDC dc(this); + CFont *pFont = GetParent()->GetFont(); + CFont *pFontDC = dc.SelectObject( pFont ); + CSize size = dc.GetTextExtent( str ); + dc.SelectObject( pFontDC ); + size.cx += 5; // add some extra buffer + + // Get client rect + CRect rect, parentrect; + GetClientRect( &rect ); + GetParent()->GetClientRect( &parentrect ); + + // Transform rect to parent coordinates + ClientToScreen( &rect ); + GetParent()->ScreenToClient( &rect ); + + // Check whether control needs to be resized + // and whether there is space to grow + if( size.cx > rect.Width() ) + { + if( size.cx + rect.left < parentrect.right ) + rect.right = rect.left + size.cx; + else + rect.right = parentrect.right; + MoveWindow( &rect ); + } +} + +int CInPlaceEdit::OnCreate(LPCREATESTRUCT lpCreateStruct) +{ + if (CEdit::OnCreate(lpCreateStruct) == -1) + return -1; + + // Set the proper font + CFont* font = GetParent()->GetFont(); + SetFont(font); + + SetWindowText( m_sInitText ); + SetFocus(); + SetSel( 0, -1 ); + return 0; +} diff --git a/Src/SubeditList.h b/Src/SubeditList.h new file mode 100644 index 000000000..e7d9ba20c --- /dev/null +++ b/Src/SubeditList.h @@ -0,0 +1,90 @@ +#if !defined(AFX_SUBEDITLIST_H__335134F3_37B4_4739_AC9B_4AFB32C37E60__INCLUDED_) +#define AFX_SUBEDITLIST_H__335134F3_37B4_4739_AC9B_4AFB32C37E60__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// SubeditList.h : header file +// + +class CInPlaceEdit : public CEdit +{ +// Construction +public: + CInPlaceEdit(int iItem, int iSubItem, CString sInitText); + +// Attributes +public: + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CInPlaceEdit) + public: + virtual BOOL PreTranslateMessage(MSG* pMsg); + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CInPlaceEdit(); + + // Generated message map functions +protected: + //{{AFX_MSG(CInPlaceEdit) + afx_msg void OnKillFocus(CWnd* pNewWnd); + afx_msg void OnNcDestroy(); + afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + //}}AFX_MSG + + DECLARE_MESSAGE_MAP() +private: + int m_iItem; + int m_iSubItem; + CString m_sInitText; + BOOL m_bESC; // To indicate whether ESC key was pressed +}; + +class CSubeditList : public CListCtrl +{ +// Construction +public: + CSubeditList(); + +// Attributes +public: + +// Operations +public: + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSubeditList) + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CSubeditList(); + + CInPlaceEdit *EditSubLabel(int nItem, int nCol); + // Generated message map functions +//protected: + //{{AFX_MSG(CSubeditList) + // NOTE - the ClassWizard will add and remove member functions here. + afx_msg int HitTestEx(CPoint& point, int* col) const; + afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); + afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); + afx_msg void OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnBeginLabelEdit(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + //}}AFX_MSG + + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SUBEDITLIST_H__335134F3_37B4_4739_AC9B_4AFB32C37E60__INCLUDED_) diff --git a/Src/TokenPairList.cpp b/Src/TokenPairList.cpp new file mode 100644 index 000000000..e7411419c --- /dev/null +++ b/Src/TokenPairList.cpp @@ -0,0 +1,180 @@ +/** + * @file TokenPairList.cpp + * + * @brief Implementation for IgnoredSubstitutionsFiltersList class. + */ + +#include "pch.h" +#include "TokenPairList.h" +#include +#include +#include "OptionsMgr.h" +#include "UnicodeString.h" + +using std::vector; + +/** @brief Registry key for saving Ignored Substitutions filters. */ +static const TCHAR IgnoredSubstitutionsRegPath[] = _T("IgnoredSubstitutions"); + +/** + * @brief Default constructor. + */ +TokenPairList::TokenPairList() +: m_pOptionsMgr(nullptr) +{ +} + +/** + * @brief Destructor, empties the list. + */ +TokenPairList::~TokenPairList() +{ +} + +/** + * @brief Add new filter to the list. + * @param [in] filter Filter string to add. + * @param [in] enabled Is filter enabled? + */ +void TokenPairList::AddFilter(const String& filter0, const String& filter1) +{ + std::shared_ptr item(new TokenPair()); + item->filterStr0 = filter0; + item->filterStr1 = filter1; + m_items.push_back(item); +} + +/** + * @brief Return filter from given index. + * @param [in] ind Index of filter. + * @return Filter item from the index. If the index is beyond table limit, + * return the last item in the list. + */ +const TokenPair & TokenPairList::GetAt(size_t ind) const +{ + if (ind < m_items.size()) + return *m_items[ind]; + else + return *m_items.back(); +} + +/** + * @brief Clone filter list from another list. + * This function clones filter list from another list. Current items in the + * list are removed and new items added from the given list. + * @param [in] list List to clone. + */ +void TokenPairList::CloneFrom(const TokenPairList *list) +{ + Empty(); + size_t count = list->GetCount(); + + for (size_t i = 0; i < count; i++) + { + const TokenPair &item = list->GetAt(i); + AddFilter(item.filterStr0, item.filterStr1); + } +} + +/** + * @brief Compare filter lists. + * @param [in] list List to compare. + * @return true if lists are identical, false otherwise. + */ +bool TokenPairList::Compare(const TokenPairList *list) const +{ + if (list->GetCount() != GetCount()) + return false; + + for (size_t i = 0; i < GetCount(); i++) + { + const TokenPair &item1 = list->GetAt(i); + const TokenPair &item2 = GetAt(i); + + if + ( + item1.filterStr0 != item2.filterStr0 + || item1.filterStr1 != item2.filterStr1 + ) + return false; + } + return true; +} + +/** + * @brief Read filter list from the options system. + * @param [in] pOptionsMgr Pointer to options system. + */ +void TokenPairList::Initialize(COptionsMgr *pOptionsMgr) +{ + assert(pOptionsMgr != nullptr); + String valuename(IgnoredSubstitutionsRegPath); + + m_pOptionsMgr = pOptionsMgr; + + size_t count = m_items.size(); + valuename += _T("/Values"); + m_pOptionsMgr->InitOption(valuename, static_cast(count)); + count = m_pOptionsMgr->GetInt(valuename); + + for (unsigned i = 0; i < count; i++) + { + String name0 = strutils::format(_T("%s/Filter%02u_0"), IgnoredSubstitutionsRegPath, i); + m_pOptionsMgr->InitOption(name0, _T("")); + String filterStr0 = m_pOptionsMgr->GetString(name0); + + String name1 = strutils::format(_T("%s/Filter%02u_1"), IgnoredSubstitutionsRegPath, i); + m_pOptionsMgr->InitOption(name1, _T("")); + String filterStr1 = m_pOptionsMgr->GetString(name1); + + AddFilter(filterStr0, filterStr1); + } +} + +/** + * @brief Save Ignored Substitutions to options system. + */ +void TokenPairList::SaveFilters() +{ + assert(m_pOptionsMgr != nullptr); + String valuename(IgnoredSubstitutionsRegPath); + + size_t count = m_items.size(); + valuename += _T("/Values"); + m_pOptionsMgr->SaveOption(valuename, static_cast(count)); + + for (size_t i = 0; i < count; i++) + { + const std::shared_ptr &item = m_items[i]; + + String name0 = strutils::format(_T("%s/Filter%02u_0"), IgnoredSubstitutionsRegPath, i); + m_pOptionsMgr->InitOption(name0, _T("")); + m_pOptionsMgr->SaveOption(name0, item->filterStr0); + + String name1 = strutils::format(_T("%s/Filter%02u_1"), IgnoredSubstitutionsRegPath, i); + m_pOptionsMgr->InitOption(name1, _T("")); + m_pOptionsMgr->SaveOption(name1, item->filterStr1); + } + + // Remove options we don't need anymore + // We could have earlier 10 pcs but now we only need 5 + String filter = strutils::format(_T("%s/Enabled%02u"), IgnoredSubstitutionsRegPath, count); + int retval = m_pOptionsMgr->RemoveOption(filter); + + String filter0 = strutils::format(_T("%s/Filter%02u_0"), IgnoredSubstitutionsRegPath, count); + int retval0 = m_pOptionsMgr->RemoveOption(filter0); + + String filter1 = strutils::format(_T("%s/Filter%02u_1"), IgnoredSubstitutionsRegPath, count); + int retval1 = m_pOptionsMgr->RemoveOption(filter1); + + while (retval == COption::OPT_OK || retval0 == COption::OPT_OK || retval1 == COption::OPT_OK) + { + ++count; + filter = strutils::format(_T("%s/Enabled%02u"), IgnoredSubstitutionsRegPath, count); + retval = m_pOptionsMgr->RemoveOption(filter); + filter0 = strutils::format(_T("%s/Filter%02u_0"), IgnoredSubstitutionsRegPath, count); + retval0 = m_pOptionsMgr->RemoveOption(filter); + filter1 = strutils::format(_T("%s/Filter%02u_1"), IgnoredSubstitutionsRegPath, count); + retval1 = m_pOptionsMgr->RemoveOption(filter); + } +} diff --git a/Src/TokenPairList.h b/Src/TokenPairList.h new file mode 100644 index 000000000..310edb04e --- /dev/null +++ b/Src/TokenPairList.h @@ -0,0 +1,62 @@ +/** + * @file IgnoredSubstitutionsFiltersList.h + * + * @brief Declaration file for IgnoredSubstitutionsFiltersList class + */ +#pragma once + +#include +#include +#include "UnicodeString.h" + +class COptionsMgr; + +/** + @brief Structure for filter. + */ +struct TokenPair +{ + String filterStr0; + String filterStr1; +}; + +/** + @brief List of raw Ignored Substitution pairs. + */ +class TokenPairList +{ +public: + TokenPairList(); + ~TokenPairList(); + + void AddFilter(const String& filter0, const String& filter1); + size_t GetCount() const; + void Empty(); + const TokenPair &GetAt(size_t ind) const; + void CloneFrom(const TokenPairList *list); + bool Compare(const TokenPairList *list) const; + + void Initialize(COptionsMgr *pOptionsMgr); + void SaveFilters(); + +private: + std::vector> m_items; /**< List for linefilter items */ + COptionsMgr * m_pOptionsMgr; /**< Options-manager for storage */ +}; + +/** + * @brief Returns count of items in the list. + * @return Count of filters in the list. + */ +inline size_t TokenPairList::GetCount() const +{ + return m_items.size(); +} + +/** + * @brief Empties the list. + */ +inline void TokenPairList::Empty() +{ + m_items.clear(); +} diff --git a/Src/resource.h b/Src/resource.h index 925e0ebe2..f4d93cefc 100644 --- a/Src/resource.h +++ b/Src/resource.h @@ -22,6 +22,7 @@ #define IDD_OPEN 202 #define IDD_PROPPAGE_GENERAL 205 #define IDD_PROPPAGE_FILTER 207 +#define IDD_IGNORED_SUSBSTITUTIONS_DLG 208 #define IDD_PROPPAGE_SYSTEM 209 #define IDD_EDITOR_HEADERBAR 210 #define IDD_GENERATE_PATCH 211 @@ -190,6 +191,7 @@ #define IDC_ALL_WHITE 1027 #define IDC_WHITE_CHANGE 1028 #define IDC_WHITESPACE 1029 +#define IDC_IGNORED_SUSBSTITUTIONS_ARE_ENABLED 1030 #define IDC_EOL_SENSITIVE 1032 #define IDC_CP_SENSITIVE 1033 #define IDC_DIFFERENCE_COLOR 1035 @@ -455,6 +457,7 @@ #define IDC_LFILTER_ADDBTN 1321 #define IDC_LFILTER_EDITBTN 1322 #define IDC_LFILTER_REMOVEBTN 1323 +#define IDC_IGNORED_SUBSTITUTIONS_FILTER 1324 #define IDC_ASK_MULTIWINDOW_CLOSE 1326 #define IDC_COLDLG_LIST 1327 #define IDC_PRESERVE_FILETIME 1328 @@ -557,6 +560,10 @@ #define IDC_INDENT_HEURISTIC 8829 #define IDC_LIST_FILE 8830 #define IDC_FLDCONFIRM_DONTASKAGAIN 8831 +#define IDC_IGNORED_SUSBSTITUTIONS_WORK_BOTH_WAYS 8832 +#define IDC_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS 8833 +#define IDC_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS 8834 +#define IDC_LFILTER_CLEARBTN 8836 #define IDS_SPLASH_DEVELOPERS 8976 #define IDS_SPLASH_GPLTEXT 8977 #define IDS_MESSAGEBOX_OK 9001 @@ -1350,6 +1357,7 @@ #define ID_SWAPPANES_SWAP12 34170 #define ID_SWAPPANES_SWAP23 34171 #define ID_SWAPPANES_SWAP13 34172 +#define ID_ADD_TO_IGNORED_SUBSTITUTIONS 34173 // Next default values for new objects // @@ -1357,7 +1365,7 @@ #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_3D_CONTROLS 1 #define _APS_NEXT_RESOURCE_VALUE 253 -#define _APS_NEXT_COMMAND_VALUE 34173 +#define _APS_NEXT_COMMAND_VALUE 34174 #define _APS_NEXT_CONTROL_VALUE 8832 #define _APS_NEXT_SYMED_VALUE 117 #endif diff --git a/Src/subedit.h b/Src/subedit.h new file mode 100644 index 000000000..e869d2561 --- /dev/null +++ b/Src/subedit.h @@ -0,0 +1,49 @@ +// subedit.h : main header file for the SUBEDIT application +// + +#if !defined(AFX_SUBEDIT_H__071E4499_6320_49E5_B602_E6F7873ED145__INCLUDED_) +#define AFX_SUBEDIT_H__071E4499_6320_49E5_B602_E6F7873ED145__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CSubeditApp: +// See subedit.cpp for the implementation of this class +// + +class CSubeditApp : public CWinApp +{ +public: + CSubeditApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSubeditApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + //{{AFX_MSG(CSubeditApp) + afx_msg void OnAppAbout(); + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SUBEDIT_H__071E4499_6320_49E5_B602_E6F7873ED145__INCLUDED_)