View Issue Details

IDProjectCategoryView StatusLast Update
0002294WishesEditorpublic2017-11-11 22:36
ReporterMikeMirzayanov Assigned ToDrKnS  
PrioritynormalSeverityminorReproducibilityN/A
Status assignedResolutionopen 
Product Version3.0 
Summary0002294: Идея и реализация PreserveStyle в SearchReplace (есть патч)
DescriptionПредлагаю добавить режим «умной» замены в поиск-замену в редакторе. Прилагаю полный diff с реализацией. Вроде только не поправил документацию и не протащил PreserveStyle в EditorOptions (не уверен, что это надо). Смогу сделать и то и другое, если есть заинтересованность в использовании моего патча.

Идея.

Редактируя исходные тексты (и не только), часто надо делать замену с сохранением стиля написания (что-то вроде рефакторинга «rename»).

Простой пример. Во фразе «A number of a bytes» надо заменить «the» на «a». В идеале одной заменой надо получить «The number of the bytes». Именно с таким регистром символов.

Пример сложнее. Пусть есть следующий гипотетический код (написан просто для примера):
public class UserName {
    public static final int MAX_USER_NAME_SIZE = 100;

    /* Я так не называю поля в Java, странное имя - для полноты примера :) */
    private String m_userNameText = null;

    public void setUserNameText(String userNameText) {
        m_userNameText = userNameText;
    }

    public String getUserNameText() {
        if (m_userNameText == null) {
            return ConfigurationProperties.get("default_user_name")
                    || ConfigurationProperties.get("default.user.name")
                    || ConfigurationProperties.get("default-user-name");
        }

        return m_userNameText;
    }
}

В нем хочется заменить «UserName» на «PersonalLoginId» и получить такое (обратите внимание на регистры и стилизацию всех упоминаний PersonalLoginId):
public class PersonalLoginId {
    public static final int MAX_PERSONAL_LOGIN_ID_SIZE = 100;

    /* Я так не называю поля в Java, странное имя - для полноты примера :) */
    private String m_personalLoginIdText = null;

    public void setPersonalLoginIdText(String personalLoginIdText) {
        m_personalLoginIdText = personalLoginIdText;
    }

    public String getPersonalLoginIdText() {
        if (m_personalLoginIdText == null) {
            return ConfigurationProperties.get("default_personal_login_id")
                    || ConfigurationProperties.get("default.personal.login.id")
                    || ConfigurationProperties.get("default-personal-login-id");
        }

        return m_personalLoginIdText;
    }
}

Т.е. при замене угадывается стиль того что заменяется и заменяемый текст под этот стиль преобразуется. Оба примера работают в одну замену в пропатченном мной FAR-е.
Additional InformationЕсли надо что-то доработать/исправить/пояснить, сделаю всё что надо.

Может добавить аналогичную тему в форум в Предложения и идеи, послушать что люди скажут?
TagsNo tags attached.
Build

Activities

MikeMirzayanov

2013-02-08 23:57

reporter  

PreserveStyle.diff (26,005 bytes)   
Index: unicode_far/stddlg.hpp
===================================================================
--- unicode_far/stddlg.hpp	(revision 9550)
+++ unicode_far/stddlg.hpp	(working copy)
@@ -82,6 +82,9 @@
     Regexp
       ����� �� ���������, ������� �� ������� ���� "Regular expressions"
 
+    Regexp
+      ����� �� ���������, ������� �� ������� ���� "Preserve style"
+
     HelpTopic
       �� ��� �����.
       ���� nullptr ��� ����� ������ - ���� ����� �� ���������.
@@ -104,6 +107,7 @@
     bool* WholeWords,
     bool* Reverse,
     bool* Regexp,
+    bool* PreserveStyle,
     const wchar_t *HelpTopic=nullptr,
     bool HideAll=false
 );
Index: unicode_far/editor.hpp
===================================================================
--- unicode_far/editor.hpp	(revision 9550)
+++ unicode_far/editor.hpp	(working copy)
@@ -215,7 +215,7 @@
 		/* $ 30.07.2000 KM
 		   ���� ��������� �� ������ "Whole words"
 		*/
-		bool LastSearchCase,LastSearchWholeWords,LastSearchReverse, LastSearchRegexp;
+		bool LastSearchCase,LastSearchWholeWords,LastSearchReverse,LastSearchRegexp,LastSearchPreserveStyle;
 
 		uintptr_t m_codepage; //BUGBUG
 
Index: unicode_far/preservestyle.hpp
===================================================================
--- unicode_far/preservestyle.hpp	(revision 0)
+++ unicode_far/preservestyle.hpp	(working copy)
@@ -0,0 +1,3 @@
+#pragma once
+
+bool PreserveStyleReplaceString(const wchar_t *Source, int StrSize, const string& Str, string& ReplaceStr, int& CurPos, int Position, int Case, int WholeWords, const wchar_t *WordDiv, int Reverse, int& SearchLength);
Index: unicode_far/strmix.cpp
===================================================================
--- unicode_far/strmix.cpp	(revision 9550)
+++ unicode_far/strmix.cpp	(working copy)
@@ -39,6 +39,7 @@
 #include "language.hpp"
 #include "config.hpp"
 #include "pathmix.hpp"
+#include "preservestyle.hpp"
 
 string &FormatNumber(const wchar_t *Src, string &strDest, int NumDigits)
 {
@@ -1381,13 +1382,16 @@
 	return (UuidFromString((unsigned short*)Value,&Guid)==RPC_S_OK)?true:false;
 }
 
-bool SearchString(const wchar_t *Source, int StrSize, const string& Str, string& ReplaceStr,int& CurPos, int Position,int Case,int WholeWords,int Reverse,int Regexp, int *SearchLength,const wchar_t* WordDiv)
+bool SearchString(const wchar_t *Source, int StrSize, const string& Str, string& ReplaceStr,int& CurPos, int Position,int Case,int WholeWords,int Reverse,int Regexp,int PreserveStyle, int *SearchLength,const wchar_t* WordDiv)
 {
 	*SearchLength = 0;
 
 	if (!WordDiv)
 		WordDiv=Global->Opt->strWordDiv;
 
+	if (!Regexp && PreserveStyle && PreserveStyleReplaceString(Source, StrSize, Str, ReplaceStr, CurPos, Position, Case, WholeWords, WordDiv, Reverse, *SearchLength))
+		return true;
+
 	if (Reverse)
 	{
 		Position--;
@@ -1467,6 +1471,17 @@
 				if (!Str[J])
 				{
 					CurPos=I;
+
+					// � ����� PreserveStyle: ���� �� �������� ������ ������ c ����� PreserveStyleReplaceString,
+					// �� ��� � ������� ������� ������ ����.
+					if (PreserveStyle && !ReplaceStr.IsEmpty() && IsAlpha(ReplaceStr[0]) && IsAlpha(Source[I]))
+					{
+						if (IsUpper(Source[I]))
+							ReplaceStr.GetBuffer()[0] = Upper(ReplaceStr[0]);
+						if (IsLower(Source[I]))
+							ReplaceStr.GetBuffer()[0] = Lower(ReplaceStr[0]);
+					}
+					
 					return true;
 				}
 
Index: unicode_far/far.vc11.vcxproj.filters
===================================================================
--- unicode_far/far.vc11.vcxproj.filters	(revision 9550)
+++ unicode_far/far.vc11.vcxproj.filters	(working copy)
@@ -326,6 +326,9 @@
     <ClCompile Include="poscache.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="preservestyle.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="print.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -853,6 +856,9 @@
     <ClInclude Include="poscache.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="preservestyle.hpp">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="print.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
Index: unicode_far/makefile_gcc
===================================================================
--- unicode_far/makefile_gcc	(revision 9550)
+++ unicode_far/makefile_gcc	(working copy)
@@ -108,6 +108,7 @@
 	plugins.cpp \
 	plugsettings.cpp \
 	poscache.cpp \
+	preservestyle.cpp \
 	print.cpp \
 	privilege.cpp \
 	processname.cpp \
Index: unicode_far/help.cpp
===================================================================
--- unicode_far/help.cpp	(revision 9550)
+++ unicode_far/help.cpp	(working copy)
@@ -1339,7 +1339,7 @@
 
 				string strTempStr;
 				//int RetCode = GetString(MSG(MHelpSearchTitle),MSG(MHelpSearchingFor),L"HelpSearch",strLastSearchStr,strLastSearchStr0);
-				int RetCode = GetSearchReplaceString(false, MSG(MHelpSearchTitle), MSG(MHelpSearchingFor), strLastSearchStr0, strTempStr, L"HelpSearch", L"", &Case, &WholeWords, nullptr, &Regexp,nullptr,true);
+				int RetCode = GetSearchReplaceString(false, MSG(MHelpSearchTitle), MSG(MHelpSearchingFor), strLastSearchStr0, strTempStr, L"HelpSearch", L"", &Case, &WholeWords, nullptr, &Regexp, nullptr, nullptr, true);
 
 				if (RetCode <= 0)
 					return TRUE;
@@ -1908,7 +1908,7 @@
 			string ReplaceStr;
 			int CurPos=0;
 			int SearchLength;
-			bool Result=SearchString(strReadStr,(int)strReadStr.GetLength(),strLastSearchStr,ReplaceStr,CurPos,0,LastSearchCase,LastSearchWholeWords,false,LastSearchRegexp,&SearchLength);
+			bool Result=SearchString(strReadStr,(int)strReadStr.GetLength(),strLastSearchStr,ReplaceStr,CurPos,0,LastSearchCase,LastSearchWholeWords,false,false,LastSearchRegexp,&SearchLength);
 
 			if (Result)
 			{
Index: unicode_far/makefile_vc
===================================================================
--- unicode_far/makefile_vc	(revision 9550)
+++ unicode_far/makefile_vc	(working copy)
@@ -200,6 +200,7 @@
 	"$(INTDIR)\plugins.obj" \
 	"$(INTDIR)\plugsettings.obj" \
 	"$(INTDIR)\poscache.obj" \
+	"$(INTDIR)\preservestyle.obj" \
 	"$(INTDIR)\print.obj" \
 	"$(INTDIR)\privilege.obj" \
 	"$(INTDIR)\processname.obj" \
Index: unicode_far/strmix.hpp
===================================================================
--- unicode_far/strmix.hpp	(revision 9550)
+++ unicode_far/strmix.hpp	(working copy)
@@ -123,4 +123,4 @@
 string GuidToStr(const GUID& Guid);
 bool StrToGuid(const wchar_t* Value,GUID& Guid);
 
-bool SearchString(const wchar_t *Source, int StrSize, const string& Str, string& ReplaceStr,int& CurPos, int Position,int Case,int WholeWords,int Reverse,int Regexp, int *SearchLength,const wchar_t* WordDiv=nullptr);
+bool SearchString(const wchar_t *Source, int StrSize, const string& Str, string& ReplaceStr,int& CurPos, int Position,int Case,int WholeWords,int Reverse,int Regexp,int PreserveStyle, int *SearchLength,const wchar_t* WordDiv=nullptr);
Index: unicode_far/farlang.templ.m4
===================================================================
--- unicode_far/farlang.templ.m4	(revision 9550)
+++ unicode_far/farlang.templ.m4	(working copy)
@@ -4988,6 +4988,16 @@
 "Expresiones re&gulares"
 "Re&gulárne výrazy"
 
+MEditSearchPreserveStyle
+"&Сохранять стиль"
+"&Preserve style"
+upd:"&Zachovat styl"
+upd:"&Preserve Stil"
+upd:"&Megőrzése stílus"
+upd:"&Zachowaj styl"
+upd:"&Preservar estilo"
+upd:"&Zachovať štýl"
+
 MEditSearchSearch
 "Искать"
 "Search"
Index: unicode_far/far.vcxproj
===================================================================
--- unicode_far/far.vcxproj	(revision 9550)
+++ unicode_far/far.vcxproj	(working copy)
@@ -568,6 +568,7 @@
     <ClCompile Include="plugins.cpp" />
     <ClCompile Include="plugsettings.cpp" />
     <ClCompile Include="poscache.cpp" />
+    <ClCompile Include="preservestyle.cpp" />
     <ClCompile Include="print.cpp" />
     <ClCompile Include="privilege.cpp" />
     <ClCompile Include="processname.cpp" />
Index: unicode_far/edit.cpp
===================================================================
--- unicode_far/edit.cpp	(revision 9550)
+++ unicode_far/edit.cpp	(working copy)
@@ -2097,7 +2097,7 @@
    ������� ������ �������� ��-�� ������������
    ��������� ������ ��� ����.
 */
-int Edit::Search(const string& Str,string& ReplaceStr,int Position,int Case,int WholeWords,int Reverse,int Regexp, int *SearchLength)
+int Edit::Search(const string& Str,string& ReplaceStr,int Position,int Case,int WholeWords,int Reverse,int Regexp,int PreserveStyle, int *SearchLength)
 {
 #if 0
 	*SearchLength = 0;
@@ -2226,7 +2226,7 @@
 
 	return FALSE;
 #else
-	return SearchString(this->Str,this->StrSize,Str,ReplaceStr,CurPos,Position,Case,WholeWords,Reverse,Regexp,SearchLength,WordDiv());
+	return SearchString(this->Str,this->StrSize,Str,ReplaceStr,CurPos,Position,Case,WholeWords,Reverse,Regexp,PreserveStyle,SearchLength,WordDiv());
 #endif
 }
 
Index: unicode_far/far.vc11.vcxproj
===================================================================
--- unicode_far/far.vc11.vcxproj	(revision 9550)
+++ unicode_far/far.vc11.vcxproj	(working copy)
@@ -425,6 +425,7 @@
     <ClCompile Include="plugins.cpp" />
     <ClCompile Include="plugsettings.cpp" />
     <ClCompile Include="poscache.cpp" />
+    <ClCompile Include="preservestyle.cpp" />
     <ClCompile Include="print.cpp" />
     <ClCompile Include="privilege.cpp" />
     <ClCompile Include="processname.cpp" />
@@ -816,6 +817,7 @@
     <ClInclude Include="plugins.hpp" />
     <ClInclude Include="plugsettings.hpp" />
     <ClInclude Include="poscache.hpp" />
+    <ClInclude Include="preservestyle.hpp" />
     <ClInclude Include="print.hpp" />
     <ClInclude Include="privilege.hpp" />
     <ClInclude Include="processname.hpp" />
Index: unicode_far/contrib/codeblocks/Far.cbp
===================================================================
--- unicode_far/contrib/codeblocks/Far.cbp	(revision 9550)
+++ unicode_far/contrib/codeblocks/Far.cbp	(working copy)
@@ -328,6 +328,8 @@
 		<Unit filename="plugins.hpp" />
 		<Unit filename="poscache.cpp" />
 		<Unit filename="poscache.hpp" />
+		<Unit filename="preservestyle.cpp" />
+		<Unit filename="preservestyle.hpp" />
 		<Unit filename="print.cpp" />
 		<Unit filename="print.hpp" />
 		<Unit filename="processname.cpp" />
Index: unicode_far/stddlg.cpp
===================================================================
--- unicode_far/stddlg.cpp	(revision 9550)
+++ unicode_far/stddlg.cpp	(working copy)
@@ -59,6 +59,7 @@
 	bool* pWholeWords,
 	bool* pReverse,
 	bool* pRegexp,
+	bool* pPreserveStyle,
 	const wchar_t *HelpTopic,
 	bool HideAll)
 {
@@ -81,6 +82,7 @@
 	bool WholeWords=pWholeWords?*pWholeWords:false;
 	bool Reverse=pReverse?*pReverse:false;
 	bool Regexp=pRegexp?*pRegexp:false;
+	bool PreserveStyle=pPreserveStyle?*pPreserveStyle:false;
 
 
 	if (IsReplaceMode)
@@ -96,7 +98,7 @@
 		05   |                                                                   |
 		06   +--------------------------------------------------------------------+
 		07   | [ ] Case sensitive                 [ ] Regular expressions         |
-		08   | [ ] Whole words                                                    |
+		08   | [ ] Whole words                    [ ] Preserve style              |
 		09   | [ ] Reverse search                                                 |
 		10   +--------------------------------------------------------------------+
 		11   |                      [ Replace ]  [ Cancel ]                       |
@@ -115,6 +117,7 @@
 			{DI_CHECKBOX,5,8,0,8,WholeWords,nullptr,nullptr,0,MSG(MEditSearchWholeWords)},
 			{DI_CHECKBOX,5,9,0,9,Reverse,nullptr,nullptr,0,MSG(MEditSearchReverse)},
 			{DI_CHECKBOX,40,7,0,7,Regexp,nullptr,nullptr,0,MSG(MEditSearchRegexp)},
+			{DI_CHECKBOX,40,8,0,8,PreserveStyle,nullptr,nullptr,0,MSG(MEditSearchPreserveStyle)},
 			{DI_TEXT,-1,10,0,10,0,nullptr,nullptr,DIF_SEPARATOR,L""},
 			{DI_BUTTON,0,11,0,11,0,nullptr,nullptr,DIF_DEFAULTBUTTON|DIF_CENTERGROUP,MSG(MEditReplaceReplace)},
 			{DI_BUTTON,0,11,0,11,0,nullptr,nullptr,DIF_CENTERGROUP,MSG(MEditSearchCancel)},
@@ -129,6 +132,8 @@
 			ReplaceDlg[8].Flags |= DIF_DISABLE; // DIF_HIDDEN ??
 		if (!pRegexp)
 			ReplaceDlg[9].Flags |= DIF_DISABLE; // DIF_HIDDEN ??
+		if (!pPreserveStyle)
+			ReplaceDlg[10].Flags |= DIF_DISABLE; // DIF_HIDDEN ??
 
 		Dialog Dlg(ReplaceDlg,ARRAYSIZE(ReplaceDlgData));
 		Dlg.SetPosition(-1,-1,76,14);
@@ -138,7 +143,7 @@
 
 		Dlg.Process();
 
-		if(Dlg.GetExitCode() == 11)
+		if(Dlg.GetExitCode() == 12)
 		{
 			Result = 1;
 			SearchStr = ReplaceDlg[2].strData;
@@ -147,6 +152,7 @@
 			WholeWords=ReplaceDlg[7].Selected == BSTATE_CHECKED;
 			Reverse=ReplaceDlg[8].Selected == BSTATE_CHECKED;
 			Regexp=ReplaceDlg[9].Selected == BSTATE_CHECKED;
+			PreserveStyle=ReplaceDlg[10].Selected == BSTATE_CHECKED;
 		}
 	}
 	else
@@ -223,6 +229,8 @@
 		*pReverse=Reverse;
 	if (pRegexp)
 		*pRegexp=Regexp;
+	if (pPreserveStyle)
+		*pPreserveStyle=PreserveStyle;
 
 	return Result;
 }
Index: unicode_far/editor.cpp
===================================================================
--- unicode_far/editor.cpp	(revision 9550)
+++ unicode_far/editor.cpp	(working copy)
@@ -88,6 +88,7 @@
 	LastSearchWholeWords(Global->GlobalSearchWholeWords),
 	LastSearchReverse(Global->GlobalSearchReverse),
 	LastSearchRegexp(Global->Opt->EdOpt.SearchRegexp),
+	LastSearchPreserveStyle(false),
 	m_codepage(CP_DEFAULT),
 	StartLine(-1),
 	StartChar(-1),
@@ -3681,7 +3682,7 @@
 	string strMsgStr;
 	const wchar_t *TextHistoryName=L"SearchText",*ReplaceHistoryName=L"ReplaceText";
 	int CurPos, NewNumLine;
-	bool Case,WholeWords,ReverseSearch,Regexp,Match,UserBreak;
+	bool Case,WholeWords,ReverseSearch,Regexp,PreserveStyle,Match,UserBreak;
 	AutoUndoBlock UndoBlock(this);
 
 	if (Next && strLastSearchStr.IsEmpty())
@@ -3692,6 +3693,7 @@
 	Case=LastSearchCase;
 	WholeWords=LastSearchWholeWords;
 	ReverseSearch=LastSearchReverse;
+	PreserveStyle=LastSearchPreserveStyle;
 	Regexp=LastSearchRegexp;
 
 	bool FindAllReferences = false;
@@ -3710,7 +3712,7 @@
 			}
 		}
 
-		int DlgResult = GetSearchReplaceString(ReplaceMode, nullptr, nullptr, strSearchStr, strReplaceStr, TextHistoryName, ReplaceHistoryName, &Case, &WholeWords, &ReverseSearch, &Regexp, L"EditorSearch");
+		int DlgResult = GetSearchReplaceString(ReplaceMode, nullptr, nullptr, strSearchStr, strReplaceStr, TextHistoryName, ReplaceHistoryName, &Case, &WholeWords, &ReverseSearch, &Regexp, &PreserveStyle, L"EditorSearch");
 		if (!DlgResult)
 		{
 			return FALSE;
@@ -3727,6 +3729,7 @@
 	LastSearchWholeWords=WholeWords;
 	LastSearchReverse=ReverseSearch;
 	LastSearchRegexp=Regexp;
+	LastSearchPreserveStyle=PreserveStyle;
 
 	if(FindAllReferences)
 	{
@@ -3808,7 +3811,7 @@
 			int SearchLength=0;
 			string strReplaceStrCurrent(ReplaceMode?strReplaceStr:L"");
 
-			if (CurPtr->Search(strSearchStr,strReplaceStrCurrent,CurPos,Case,WholeWords,ReverseSearch,Regexp,&SearchLength))
+			if (CurPtr->Search(strSearchStr,strReplaceStrCurrent,CurPos,Case,WholeWords,ReverseSearch,Regexp,PreserveStyle,&SearchLength))
 			{
 				Match=1;
 				Edit *FoundPtr = CurPtr;
Index: unicode_far/mkdep.list
===================================================================
--- unicode_far/mkdep.list	(revision 9550)
+++ unicode_far/mkdep.list	(working copy)
@@ -209,6 +209,8 @@
 plugsettings.hpp
 poscache.cpp
 poscache.hpp
+preservestyle.cpp
+preservestyle.hpp
 print.cpp
 print.hpp
 privilege.cpp
Index: unicode_far/preservestyle.cpp
===================================================================
--- unicode_far/preservestyle.cpp	(revision 0)
+++ unicode_far/preservestyle.cpp	(working copy)
@@ -0,0 +1,326 @@
+#include "headers.hpp"
+#pragma hdrstop
+
+#include "preservestyle.hpp"
+
+static const wchar_t *PreserveStyleTokenSeparators = L"_-.";
+
+enum PreserveStyleType
+{
+	UPPERCASE_ALL,
+	LOWERCASE_ALL,
+	UPPERCASE_FIRST,
+	UNKNOWN
+};
+
+struct PreserveStyleToken
+{
+	string Token;
+	wchar_t PrependChar;
+	int TypeMask;
+};
+
+static inline bool IsPreserveStyleTokenSeparator(wchar_t C)
+{
+	return wcschr(PreserveStyleTokenSeparators, C) != 0;
+}
+
+static int GetPeserveCaseStyleMask(const string& strStr)
+{
+	int Result = 15;
+
+	for (size_t I = 0; I < strStr.GetLength(); I++)
+	{
+		int Upper = IsUpper(strStr[I]);
+		int Lower = IsLower(strStr[I]);
+		if (!Upper)
+			Result &= ~(1 << UPPERCASE_ALL);
+		if (!Lower)
+			Result &= ~(1 << LOWERCASE_ALL);
+		if ((!Upper || !IsAlpha(strStr[I])) && I == 0)
+			Result &= ~(1 << UPPERCASE_FIRST);
+		if (!Lower && I > 0)
+			Result &= ~(1 << UPPERCASE_FIRST);
+	}
+
+	return Result;
+}
+
+static std::vector<PreserveStyleToken> InternalPreserveStyleTokenize(const string& strStr, int From, int Length)
+{
+	std::vector<PreserveStyleToken> Result;
+
+	wchar_t Sep = 0;
+	std::vector<bool> Seps(Length, false);
+	for (int I = From+1; I+1 < From+Length; I++)
+		if (IsPreserveStyleTokenSeparator(strStr[I])
+				&& !IsPreserveStyleTokenSeparator(strStr[I-1])
+				&& !IsPreserveStyleTokenSeparator(strStr[I+1]))
+		{
+			if (Sep != 0 && strStr[I] != Sep)
+			{
+				PreserveStyleToken T;
+				T.Token = strStr.SubStr(From, Length);
+				T.PrependChar = 0;
+				T.TypeMask = 1 << UNKNOWN;
+				Result.push_back(T);
+				return Result;
+			}
+
+			Seps[I-From] = true;
+		}
+
+	int L = From;
+	for (int I = From+1; I < From+Length; I++)
+	{
+		if (Seps[I-From])
+		{
+			PreserveStyleToken T;
+			T.Token = strStr.SubStr(L, I-L);
+			T.PrependChar = 0;
+			if (L-1-From >= 0 && Seps[L-1-From])
+				T.PrependChar = strStr[L-1];
+			Result.push_back(T);
+			L = I+1;
+			I++;
+			continue;
+		}
+
+		if (!Seps[I-From-1] && IsLower(strStr[I-1]) && IsUpper(strStr[I]))
+		{
+			PreserveStyleToken T;
+			T.Token = strStr.SubStr(L, I-L);
+			T.PrependChar = 0;
+			if (L-1-From >= 0 && Seps[L-1-From])
+				T.PrependChar = strStr[L-1];
+			Result.push_back(T);
+			L = I;
+		}
+	}
+
+	if (L < From+Length)
+	{
+		PreserveStyleToken T;
+		T.Token = strStr.SubStr(L, From+Length-L);
+		T.PrependChar = 0;
+		if (L-1-From >= 0 && Seps[L-1-From])
+			T.PrependChar = strStr[L-1];
+		Result.push_back(T);
+	}
+
+	if (Result.size() > 1)
+	{
+		wchar_t PrependChar = Result[1].PrependChar;
+		for (size_t I = 2; I < Result.size(); I++)
+			if (PrependChar != Result[I].PrependChar)
+			{
+				Result.clear();
+				PreserveStyleToken T;
+				T.Token = strStr.SubStr(From, Length);
+				T.PrependChar = 0;
+				T.TypeMask = 1 << UNKNOWN;
+				Result.push_back(T);
+				return Result;
+			}
+	}
+
+	return Result;
+}
+
+static std::vector<PreserveStyleToken> PreserveStyleTokenize(const string& strStr, int From, int Length)
+{
+	std::vector<PreserveStyleToken> Tokens = InternalPreserveStyleTokenize(strStr, From, Length);
+
+	int Mask = -1;
+	for (size_t I = 0; I < Tokens.size(); I++)
+	{
+		if (I == 0)
+			Tokens[I].TypeMask = GetPeserveCaseStyleMask(Tokens[I].Token);
+		else
+		{
+			if (Mask == -1)
+				Mask = GetPeserveCaseStyleMask(Tokens[I].Token);
+			else
+				Mask &= GetPeserveCaseStyleMask(Tokens[I].Token);
+		}
+	}
+
+	for (size_t I = 1; I < Tokens.size(); I++)
+		Tokens[I].TypeMask = Mask;
+
+	return Tokens;
+}
+
+static void ToPreserveStyleType(string& strStr, PreserveStyleType type)
+{
+	if (type != UNKNOWN)
+	{
+		for (size_t I = 0; I < strStr.GetLength(); I++)
+		{
+			if (type == UPPERCASE_ALL)
+				strStr.GetBuffer()[I] = Upper(strStr[I]);
+			if (type == LOWERCASE_ALL)
+				strStr.GetBuffer()[I] = Lower(strStr[I]);
+			if (type == UPPERCASE_FIRST)
+			{
+				if (I == 0)
+					strStr.GetBuffer()[I] = Upper(strStr[I]);
+				else
+					strStr.GetBuffer()[I] = Lower(strStr[I]);
+			}
+		}
+	}
+}
+
+static PreserveStyleType ChoosePreserveStyleType(int Mask)
+{
+	if (Mask == (1 << UNKNOWN))
+		return UNKNOWN;
+	
+	PreserveStyleType Result = UNKNOWN;
+	for (int Style = 0; UPPERCASE_ALL+Style < UNKNOWN; Style++)
+		if ((Mask & (1 << Style)) != 0)
+			Result = PreserveStyleType(UPPERCASE_ALL+Style);
+
+	return Result;
+}
+
+bool PreserveStyleReplaceString(const wchar_t *Source, int StrSize, const string& Str, string& ReplaceStr, int& CurPos, int Position, int Case, int WholeWords, const wchar_t *WordDiv, int Reverse, int& SearchLength)
+{
+	SearchLength = 0;
+
+	if (Reverse)
+	{
+		Position--;
+
+		if (Position>=StrSize)
+			Position=StrSize-1;
+
+		if (Position<0)
+			return false;
+	}
+
+	if (Position>=StrSize || Str.IsEmpty() || ReplaceStr.IsEmpty())
+		return false;
+
+
+	std::vector<PreserveStyleToken> StrTokens = PreserveStyleTokenize(Str, 0, Str.GetLength());
+
+	for (int I=Position; (Reverse && I>=0) || (!Reverse && I<StrSize); Reverse ? I--:I++)
+	{
+		if (WholeWords && !(I == 0 || IsSpace(Source[I-1]) || wcschr(WordDiv, Source[I-1])))
+			continue;
+
+		bool Matched = true;
+
+		int Idx = I;
+		size_t J = 0;
+		size_t T = 0;
+
+		while (((J+1 < StrTokens.size()) || (J+1 == StrTokens.size() && T < StrTokens[J].Token.GetLength()))
+			&& Source[Idx])
+		{
+			bool Sep = (I < Idx && Source[I+1] && IsPreserveStyleTokenSeparator(Source[Idx])
+					&& !IsPreserveStyleTokenSeparator(Source[Idx-1])
+					&& !IsPreserveStyleTokenSeparator(Source[Idx+1]));
+
+			if (Sep)
+			{
+				if (T == StrTokens[J].Token.GetLength())
+				{
+					Idx++;
+					T = 0;
+					J++;
+					continue;
+				}
+				else
+				{
+					Matched = false;
+					break;
+				}
+			}
+
+			Sep = (I < Idx && IsLower(Source[Idx-1]) && IsUpper(Source[Idx])
+					&& !IsPreserveStyleTokenSeparator(Source[Idx-1])
+					&& !IsPreserveStyleTokenSeparator(Source[Idx]));
+
+			if (Sep && T != 0)
+			{
+				if (T == StrTokens[J].Token.GetLength())
+				{
+					T = 0;
+					J++;
+					continue;
+				}
+				else
+				{
+					Matched = false;
+					break;
+				}
+			}
+
+			if (T >= StrTokens[J].Token.GetLength())
+			{
+				Matched = false;
+				break;
+			}
+
+			if (Case && Source[Idx] != StrTokens[J].Token[T])
+			{
+				Matched = false;
+				break;
+			}
+
+			if (!Case && Upper(Source[Idx]) != Upper(StrTokens[J].Token[T]))
+			{
+				Matched = false;
+				break;
+			}
+
+			T++;
+			Idx++;
+		}
+
+		if (WholeWords && !(Idx >= StrSize || IsSpace(Source[Idx]) || wcschr(WordDiv, Source[Idx])))
+			continue;
+		
+		if (Matched && T == StrTokens[J].Token.GetLength() && J+1 == StrTokens.size())
+		{
+			std::vector<PreserveStyleToken> SourceTokens = PreserveStyleTokenize(Source, I, Idx-I);
+			
+			bool Same = SourceTokens.size() == StrTokens.size();
+			for (size_t K = 0; Same && K < SourceTokens.size(); K++)
+				Same &= SourceTokens[K].Token.GetLength() == StrTokens[K].Token.GetLength();
+
+			if (Same)
+			{
+				std::vector<PreserveStyleToken> ReplaceStrTokens = PreserveStyleTokenize(ReplaceStr, 0, ReplaceStr.GetLength());
+				ToPreserveStyleType(ReplaceStrTokens[0].Token, ChoosePreserveStyleType(SourceTokens[0].TypeMask));
+				ReplaceStrTokens[0].PrependChar = SourceTokens[0].PrependChar;
+
+				int Last = SourceTokens.size()-1;
+				if (Last > 0)
+					for (size_t K = 1; K < ReplaceStrTokens.size(); K++)
+					{
+						ToPreserveStyleType(ReplaceStrTokens[K].Token, ChoosePreserveStyleType(SourceTokens[Last].TypeMask));
+						ReplaceStrTokens[K].PrependChar = SourceTokens[Last].PrependChar;
+					}
+
+				ReplaceStr = "";
+				for (size_t K = 0; K < ReplaceStrTokens.size(); K++)
+				{
+					if (ReplaceStrTokens[K].PrependChar != 0)
+						ReplaceStr += ReplaceStrTokens[K].PrependChar;
+					ReplaceStr += ReplaceStrTokens[K].Token;
+				}
+
+				CurPos = I;
+				SearchLength = Idx-I;
+
+				return true;
+			}
+		}
+	}
+
+	return false;		
+}
Index: unicode_far/edit.hpp
===================================================================
--- unicode_far/edit.hpp	(revision 9550)
+++ unicode_far/edit.hpp	(working copy)
@@ -113,7 +113,7 @@
 	void AppendString(const wchar_t *Str);
 	void InsertString(const wchar_t *Str);
 	void InsertBinaryString(const wchar_t *Str,int Length);
-	int Search(const string& Str,string& ReplaceStr,int Position,int Case,int WholeWords,int Reverse,int Regexp, int *SearchLength);
+	int Search(const string& Str,string& ReplaceStr,int Position,int Case,int WholeWords,int Reverse,int Regexp,int PreserveStyle, int *SearchLength);
 	void SetClearFlag(bool Flag) {Flags.Change(FEDITLINE_CLEARFLAG,Flag);}
 	int GetClearFlag() {return Flags.Check(FEDITLINE_CLEARFLAG);}
 	void SetCurPos(int NewPos) {CurPos=NewPos; SetPrevCurPos(NewPos);}
Index: unicode_far/far.vcxproj.filters
===================================================================
--- unicode_far/far.vcxproj.filters	(revision 9550)
+++ unicode_far/far.vcxproj.filters	(working copy)
@@ -326,6 +326,9 @@
     <ClCompile Include="poscache.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="preservestyle.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="print.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -853,6 +856,9 @@
     <ClInclude Include="poscache.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="preservestyle.hpp">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="print.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
PreserveStyle.diff (26,005 bytes)   

alexy

2013-02-09 00:17

administrator   bugnote:0010235

Такому место в плагине. Зачем это в фар пихать?

MikeMirzayanov

2013-02-09 00:26

reporter   bugnote:0010236

Это общая функциональность - она полезна при правке любого текста, даже литературного. Не говоря уже об исходниках. Мне кажется, что такой режим может быть часто востребован (вот Regular Expressions есть же).

alexy

2013-02-09 00:30

administrator   bugnote:0010237

Это специфичный функционал, с не совсем предвиденным результатом. В стороннем плагине (или даже макросе) ему самое место.

MikeMirzayanov

2013-02-09 00:48

reporter   bugnote:0010238

Ну ок, хотя меня не убедили. Кажется, что таким функционалом почти всегда можно пользоваться, больно уж часто проблемы как минимум с регистром при заменах. Это всегда мешает. А точки зрения пользователя даже результат Whole words тоже непредвиденный - так как какой там список сепараторов мало кто знает. А какой-нибудь встроенный Process List значительно дальше от files-and-archives, чем прокачка редактора, однако он в ядре.

Ну да ладно.

Какой плагин порекомендуете взять за основу? Что наиболее похожее из существующего уже есть, что использует современный Plugin API? Возможно ли как-то встроиться в стандартный диалог Search-Replace и процесс замены (чтобы автоматически работал доп. диалог Replace-All-Skip-Cancel)?

MikeMirzayanov

2013-02-09 00:54

reporter   bugnote:0010239

Из аргументов почему он может быть в FAR-е еще такое - эта функциональность органично встроилась в существующий функционал. Т.е. для пользователя добавилась одна галочка в одном диалоге и все. Никаких других неожиданностей нет. Она не требует доп-знаний или чтения документации, название "Preserve style" (Сохранять стиль) довольно самоописательное. Поведение довольно интуитивное.

Ну и мне просто кажется, что эта крутая фича, которая выгодно бы отличала FAR от других редакторов файлов. Вот Regular Expressions встроили же.

alexy

2013-02-09 00:57

administrator   bugnote:0010240

Можешь вырезать нужный код из самого фара и переделать его на plugin api, думаю не так уже и много работы.
Для самого близкого примера можно посмотреть на плагин EditCase.

alexy

2013-02-09 01:01

administrator   bugnote:0010241

Ну напиши на форуме, посмотрим что там скажут.

DrKnS

2013-02-09 09:47

administrator   bugnote:0010242

А мне идея нравится.

MikeMirzayanov

2013-02-09 10:08

reporter   bugnote:0010243

Спасибо. Какой план? Могу написать на форум и приложить пропатченный FAR, чтобы можно было самостоятельно без накатывания патча попробовать, как все работает.

DrKnS

2013-02-09 10:19

administrator   bugnote:0010244

Да.

DrKnS

2013-02-09 12:18

administrator   bugnote:0010260

А, и почему PreserveStyle, если это по факту PreserveCase?

MikeMirzayanov

2013-02-09 12:40

reporter   bugnote:0010261

Не совсем. Посмотрите второй пример. Он сохраняет ТАКОЙ_СПОСОБ_ИМЕНОВАНИЯ, такой_способ_именования, такой-способ-именования, такой.способ.именования и прочее (разделители настраиваются константой static const wchar_t *PreserveStyleTokenSeparators = L"_-."; в preservestyle.cpp).

Для литературных текстов он выродится в PreserveCase, а в исходниках получается помощней. Мне кажется PreserveStyle более общее и отражающее суть название.

DrKnS

2013-02-12 19:57

administrator   bugnote:0010272

Build 3182, с парой изменений, чтобы предупреждений не было.

Ждём документацию.

DAtaMan

2013-02-13 09:01

reporter   bugnote:0010277

Может, стоит сделать PreserveStyleTokenSeparators настраиваемым?
Хотя бы через far:config

MikeMirzayanov

2013-02-14 15:28

reporter  

help.diff (2,330 bytes)   
Index: unicode_far/FarRus.hlf.m4
===================================================================
--- unicode_far/FarRus.hlf.m4	(revision 9639)
+++ unicode_far/FarRus.hlf.m4	(working copy)
@@ -3001,6 +3001,8 @@
       #Регулярные выражения# - ^<wrap>использовать регулярные выражения для ~поиска~@RegExp@ и ~замены~@RegExpRepl@.
 Поиск построчный, поэтому многострочные конструкции и переводы строк не будут найдены.
 
+      #Сохранять стиль#      - ^<wrap>сохранять стиль (регистр и форматирование для файлов исходных текстов) во время ~замены~@RegExpRepl@.
+
     При нажатии кнопки #Всё# будет показано ~меню~@FindAllMenu@ с результатами поиска всех вхождений.
 
 @FindAllMenu
Index: unicode_far/FarEng.hlf.m4
===================================================================
--- unicode_far/FarEng.hlf.m4	(revision 9639)
+++ unicode_far/FarEng.hlf.m4	(working copy)
@@ -2954,11 +2954,13 @@
       #Reverse search#      - ^<wrap>change the direction of search (from the end of file towards the beginning)
 
       #Regular expressions# - ^<wrap>treat input as Perl regular expression (~search~@RegExp@ and ~replace~@RegExpRepl@).
-Поиск построчный, поэтому многострочные конструкции и переводы строк не будут найдены.
+It processes lines separately, so multi-line entries line-break characters will not be found.
 
-    При нажатии кнопки #Всё# будет показано ~меню~@FindAllMenu@ с результатами поиска всех вхождений.
+      #Preserve style#      - ^<wrap>preserve style (case and code style for program source files) (~replace~@RegExpRepl@).
 
+    By pressing the button #All# it will be shown the ~menu~@FindAllMenu@ with the whole list of matched entries.
 
+
 @FindAllMenu
 $ #Редактор: меню с результатами поиска всех вхождений#
     В этом меню доступны следующие сочетания клавиш:
help.diff (2,330 bytes)   

MikeMirzayanov

2013-02-14 15:28

reporter   bugnote:0010293

Загрузил файл help.diff с документацией.

vskirdin

2013-02-15 06:19

administrator   bugnote:0010296

Мне кажется (если следовать стилю, что "Help в Far Manager достаточно полно и понятно описывает..."), что "Preserve style" заслуживает отдельного топика в хелпе, описывающего более детально как ЭТО работает (пример - "@HardSymLink"). В дополнении к help.diff.

vskirdin

2013-02-18 05:55

administrator   bugnote:0010312

2MikeMirzayanov: так топик-разжовыватель этой фишки в хелпе будет?

MikeMirzayanov

2013-02-18 12:27

reporter   bugnote:0010313

2vskirdin: да, я напишу подробный текст

MikeMirzayanov

2013-02-27 21:48

reporter  

PreserveStyle-Description.diff (6,600 bytes)   
Index: unicode_far/FarRus.hlf.m4
===================================================================
--- unicode_far/FarRus.hlf.m4	(revision 9730)
+++ unicode_far/FarRus.hlf.m4	(working copy)
@@ -3004,10 +3004,60 @@
       #Регулярные выражения# - ^<wrap>использовать регулярные выражения для ~поиска~@RegExp@ и ~замены~@RegExpRepl@.
 Поиск построчный, поэтому многострочные конструкции и переводы строк не будут найдены.
 
-      #Сохранять стиль#      - ^<wrap>сохранять стиль (регистр и форматирование для файлов исходных текстов) во время замены.
+      ~Сохранять стиль~@PreserveStyle@      - ^<wrap>сохранять стиль (регистр и форматирование для файлов исходных текстов) во время замены.
 
     При нажатии кнопки #Всё# будет показано ~меню~@FindAllMenu@ с результатами поиска всех вхождений.
 
+
+@PreserveStyle
+$ #Режим замены: Сохранять стиль#
+
+Режим замены #"Сохранять стиль"# позволяет осуществлять замену с сохранением регистра
+заменяемого текста и/или стиля его написания для файлов исходных текстов. Например,
+при замене в этом режиме во фразе #"Tu be or not tu be"# слова #"tu"# на слово #"to"# будет
+получен результат: #"To be or not to be"# (обратите внимание на регистр). Аналогично,
+при замене #"UserName"# на #"PersonLogin"# в следующем фрагменте:
+
+    #String writerUserName = user.name || DEFAULT_USER_NAME || configuration("default-user-name");#
+
+будет получен результат:
+
+    #String writerPersonLogin = person.login || DEFAULT_PERSON_LOGIN || configuration("default-person-login");#
+
+Обратите внимание, что замена сохранила стиль именования идентификаторов для всех вхождений.
+
+Замена с сохранением стиля использует следующий алгоритм.
+    1   Образец и результат замены разбиваются на части, используя символы #"_-."# в
+        качестве разделителей, кроме того разделением считается изменение регистра
+        буквы со строчной на прописную. Разбиение осуществляется только по одному из
+        вариантов, если существует неоднозначность в выборе правила, то данная строка
+        считается одной частью. Например, строки "testMe", "WhatIsIt", "far-manager",
+        "USER_FIRST_NAME" будут разбиты на части (две, три, две и три части
+        соответственно), а строки «test_userName”, “one.two-three” разбиты не будут
+        (остануться одной частью). Все получившиеся части как образца, так и результата
+        замены не пусты (кроме тривиальных случаев пустых строк в качестве образца
+        и/или текста замены).
+    2   Осуществляется проход по тексту и поиск таких подстрок, которые при разбиении
+        на части в соответствии с пунктом 1 дают в точности те же части, что и образец
+        (при сравнении частей регистр игнорируется).
+    3   Для всех частей найденной подстроки вычисляется набор правил форматирования
+        под которые он подходит. Возможные варианты: "все прописными", "все строчными",
+        "первая заглавная, затем все строчными", "произвольный формат". Вариант
+        "произвольный формат" добавляется в набор в любом случае. Для первой части подстроки
+        набор сохраняется как есть, а остальные наборы (для всех частей кроме первой)
+        пересекаются как множества.
+    4   Набор частей результата замены преобразуется так, что первая часть форматируется
+        в соответствии с самым сильным из правил в наборе правил первой части строки,
+        а остальные - в соответствии с самым сильным из правил в найденном пересечении
+        правил.
+    5   Набор частей замены соединяется в с сохранением того разделителя, которым разделены
+        части обрабатываемой подстроки. Результат записывается на место подстроки.
+    6   Если таким способом не получилось сделать ни одной замены, то осуществляется
+        поиск-замена стандартным способом. При замене, если и в образце и в заменяемом
+        тексте первый символ – буква, то регистр этой буквы сохраняется (то есть
+        приводится к регистру первой буквы заменяемой подстроки).
+
+
 @FindAllMenu
 $ #Редактор: меню с результатами поиска всех вхождений#
     В этом меню доступны следующие сочетания клавиш:
PreserveStyle-Description.diff (6,600 bytes)   

MikeMirzayanov

2013-02-27 21:49

reporter   bugnote:0010393

Написал подробное описание на русском. Давайте отредактируем его, затем переведу на английский.

vskirdin

2013-02-28 06:00

administrator   bugnote:0010394

> 5 Набор частей замены соединяется в с сохранением того
недописано: "в с"?
Добавил немного отформатированный вариант.

vskirdin

2013-02-28 06:00

administrator  

PreserveStyle.hlf (5,134 bytes)

DrKnS

2013-03-17 12:30

administrator   bugnote:0010535

Есть, например, текст:

свистелки.и.сопелки
Свистелки.И.Сопелки
СВИСТЕЛКИ.И.СОПЕЛКИ
Свистелки.и.Сопелки

меняю "свистелки.и.сопелки" на "bells.and.whistles" со включенной опцией.

получаю
bells.and.whistles
Bells.And.Whistles
BELLS.AND.WHISTLES
Bells.and.whistles

Почему не сохранился регистр в последней строке?

MikeMirzayanov

2013-03-17 13:11

reporter   bugnote:0010536

Потому что стиль определяется одновременно для всех токенов. В данном случае есть смешение стиля и легко ошибиться, сделав неверную замену. Например, как заменять в случае, если количество токенов в образце и результате замены не совпадает?

Как вариант, можно сделать так: если кол-во токенов одинаково, то сохранять стиль токенов отдельно по одному. Кажется, что это решит эту проблему и, возможно, это будет частый способ использования.

DrKnS

2013-03-17 13:40

administrator   bugnote:0010537

> Как вариант, можно сделать так:

Я как раз об этом. Для одинакового числа токенов неопределённости нет, и, как по мне, это более ожидаемое поведение.

MikeMirzayanov

2013-03-17 14:29

reporter   bugnote:0010538

Да, сделаю такое поведение.

alexy

2013-04-28 13:20

administrator   bugnote:0010913

Что с этим?

MikeMirzayanov

2013-04-29 23:59

reporter  

PreserveStyle-Improvement.diff (5,834 bytes)   
Index: unicode_far/preservestyle.cpp
===================================================================
--- unicode_far/preservestyle.cpp	(revision 10203)
+++ unicode_far/preservestyle.cpp	(working copy)
@@ -167,30 +167,11 @@
 {
 	std::list<PreserveStyleToken> Tokens = InternalPreserveStyleTokenize(strStr, From, Length);
 
-	int Mask = -1;
-	bool First = true;
 	std::for_each(RANGE(Tokens, i)
 	{
-		if (First)
-		{
-			i.TypeMask = GetPeserveCaseStyleMask(i.Token);
-			First = false;
-		}
-		else
-		{
-			if (Mask == -1)
-				Mask = GetPeserveCaseStyleMask(i.Token);
-			else
-				Mask &= GetPeserveCaseStyleMask(i.Token);
-		}
+		i.TypeMask = GetPeserveCaseStyleMask(i.Token);
 	});
 
-	auto Start = Tokens.begin();
-	++Start;
-	std::for_each(Start, Tokens.end(), [&Mask](VALUE_TYPE(Tokens)& i)
-	{
-		i.TypeMask = Mask;
-	});
 	return Tokens;
 }
 
@@ -228,6 +209,63 @@
 	return Result;
 }
 
+void FindStyleTypeMaskAndPrependCharByExpansion(const wchar_t* Source, const size_t StrSize, const int Begin, const int End, int& TypeMask, wchar_t& PrependChar)
+{
+	/* Try to expand to the right. */
+	{
+		int Right = End;
+
+		if (static_cast<size_t>(Right) < StrSize && (IsAlphaNum(Source[Right]) || IsPreserveStyleTokenSeparator(Source[Right])))
+		{
+			Right++;
+			
+			while (static_cast<size_t>(Right) < StrSize)
+			{
+				if (!IsAlphaNum(Source[Right]) || (IsLower(Source[Right-1]) && IsUpper(Source[Right])))
+					break;
+				Right++;
+			}
+
+			const std::list<PreserveStyleToken> SegmentTokens = PreserveStyleTokenize(Source, Begin, Right-Begin);
+
+			if (SegmentTokens.size() > 1)
+			{
+				TypeMask = SegmentTokens.back().TypeMask;
+				PrependChar = SegmentTokens.back().PrependChar;
+				return;
+			}
+		}
+	}
+
+	/* Try to expand to the left. */
+	{
+		int Left = Begin - 1;
+
+		if (Left >= 1 && (IsAlphaNum(Source[Left]) || IsPreserveStyleTokenSeparator(Source[Left])))
+		{
+			Left--;
+
+			while (Left >= 0)
+			{
+				if (!IsAlphaNum(Source[Left]) || (IsLower(Source[Left]) && IsUpper(Source[Left + 1])))
+					break;
+				Left--;
+			}
+
+			Left++;
+
+			const std::list<PreserveStyleToken> SegmentTokens = PreserveStyleTokenize(Source, Left, End-Left);
+
+			if (SegmentTokens.size() > 1)
+			{
+				TypeMask = SegmentTokens.back().TypeMask;
+				PrependChar = SegmentTokens.back().PrependChar;
+				return;
+			}
+		}
+	}
+}
+
 bool PreserveStyleReplaceString(const wchar_t* Source, size_t StrSize, const string& Str, string& ReplaceStr, int& CurPos, int Position, int Case, int WholeWords, const wchar_t *WordDiv, int Reverse, int& SearchLength)
 {
 	SearchLength = 0;
@@ -338,24 +376,74 @@
 				for(auto SrcI = SourceTokens.cbegin(), StrI = StrTokens.cbegin(); Same && SrcI != SourceTokens.cend(); ++SrcI, ++StrI)
 					Same &= SrcI->Token.GetLength() == StrI->Token.GetLength();
 			}
+
 			if (Same)
 			{
 				std::list<PreserveStyleToken> ReplaceStrTokens = PreserveStyleTokenize(ReplaceStr, 0, ReplaceStr.GetLength());
-				ToPreserveStyleType(ReplaceStrTokens.front().Token, ChoosePreserveStyleType(SourceTokens.front().TypeMask));
-				ReplaceStrTokens.front().PrependChar = SourceTokens.front().PrependChar;
 
-				if (!SourceTokens.empty())
+				if (ReplaceStrTokens.size() == SourceTokens.size())
 				{
-					auto ReplaceI = ReplaceStrTokens.begin();
-					++ReplaceI;
+					int CommonTypeMask = -1;
+					std::for_each(RANGE(SourceTokens, i)
+					{
+						if (CommonTypeMask == -1)
+							CommonTypeMask = i.TypeMask;
+						else
+							CommonTypeMask &= i.TypeMask;
+					});
+
+					PreserveStyleType CommonType = ChoosePreserveStyleType(CommonTypeMask);
+					if (CommonTypeMask != -1 && CommonType == UNKNOWN)
+						CommonTypeMask = -1;
+
 					auto SourceI = SourceTokens.cbegin();
-					++SourceI;
-					for (; ReplaceI != ReplaceStrTokens.end(); ++ReplaceI, ++SourceI)
+					std::for_each(RANGE(ReplaceStrTokens, i)
 					{
-						ToPreserveStyleType(ReplaceI->Token, ChoosePreserveStyleType(SourceI->TypeMask));
-						ReplaceI->PrependChar = SourceTokens.back().PrependChar;
+						ToPreserveStyleType(i.Token, CommonTypeMask != -1 ? CommonType : ChoosePreserveStyleType(SourceI->TypeMask));
+						i.PrependChar = SourceI->PrependChar;
+						SourceI++;
+					});
+				}
+				else
+				{
+					ToPreserveStyleType(ReplaceStrTokens.front().Token, ChoosePreserveStyleType(SourceTokens.front().TypeMask));
+					ReplaceStrTokens.front().PrependChar = SourceTokens.front().PrependChar;
+
+					if (!SourceTokens.empty())
+					{
+						int AfterFirstCommonTypeMask = -1;
+						wchar_t PrependChar = SourceTokens.back().PrependChar;
+						
+						auto SourceI = SourceTokens.cbegin();
+						++SourceI;
+						for (; SourceI != SourceTokens.end(); ++SourceI)
+						{
+							if (AfterFirstCommonTypeMask == -1)
+								AfterFirstCommonTypeMask = SourceI->TypeMask;
+							else
+								AfterFirstCommonTypeMask &= SourceI->TypeMask;
+						}
+
+						if (AfterFirstCommonTypeMask == -1)
+							FindStyleTypeMaskAndPrependCharByExpansion(Source, StrSize, I, Idx, AfterFirstCommonTypeMask, PrependChar);
+
+						if (AfterFirstCommonTypeMask == -1)
+						{
+							AfterFirstCommonTypeMask = SourceTokens.front().TypeMask;
+							PrependChar = ReplaceStrTokens.back().PrependChar;
+						}
+
+						auto ReplaceI = ReplaceStrTokens.begin();
+						++ReplaceI;
+						for (; ReplaceI != ReplaceStrTokens.end(); ++ReplaceI)
+						{
+							ToPreserveStyleType(ReplaceI->Token, ChoosePreserveStyleType(AfterFirstCommonTypeMask));
+							ReplaceI->PrependChar = PrependChar;
+						}
 					}
+
 				}
+
 				ReplaceStr.Clear();
 				std::for_each(CONST_RANGE(ReplaceStrTokens, i)
 				{
PreserveStyle-Improvement.diff (5,834 bytes)   

MikeMirzayanov

2013-04-30 00:03

reporter   bugnote:0010924

Добавил PreserveStyle-Improvement.diff

В нем реализовано сохранение стилей по токенам, если их количество совпадает. Кроме того, значительно улучшено поведение, если заменяемый токен единственный.

Например, при замене "size" на "lengthOrCount" в MAX_SIZE=12 получится MAX_LENGTH_OR_COUNT, а раньше было плохо.

После того как получу подтверждение, что текущее поведение нравится, то могу актуализировать описание алгоритма в документации. Кстати, после этого надо английское описание написать.

DrKnS

2013-04-30 18:24

administrator   bugnote:0010928

3378

MikeMirzayanov

2013-04-30 18:53

reporter   bugnote:0010929

Я не уверен в корректности некоторых ваших правок. Например, замена int на size_t в строке "size_t Left = Begin - 1;" может быть небезопасна. При Begin=0 получим переполнение. Кроме того, код в FindStyleTypeMaskAndPrependCharByExpansion для двух случаев был максимально симметричен, а теперь стал разным. В этой же функции случай удлинения влево (второй) мне кажется исправлен неправильно. Например, мой цикл "while (Left >= 0)" завершался с постусловием, что Left указывает на 1 позицию левее разрыва (поэтому затем шел Left++), в от время как в вашем варианте цикл "for (;;)" благодаря "if (!Left) break;" завершится в Left=0 (если явного разрыва не нашлось). Это нарушает постусловие и дальше код уже неверен (например, Left++ уже делать не надо бы).

Возможно есть еще проблемы, я внимательно пока не все прочитал. Кстати, я бы к основной функции preservestyle.cpp написал бы тестов - это было бы полезно. Есть инфраструктура для тестов? Куда и как их лучше написать?

DrKnS

2013-04-30 19:22

administrator   bugnote:0010930

Там был варнинг, я поправил как смог :)

В 3379 должно быть как надо.


Тесты - это здорово, но инфраструктуры пока нет, так что всё обсуждаемо.

MikeMirzayanov

2013-04-30 19:48

reporter   bugnote:0010931

Да, теперь ОК.

Кстати, а зачем всюду исправили std::vector на std::list. Конечно, память может чуток экономите, но итерации по std::list значительно медленнее из-за cache miss + проще ухудшить производительность за счет O(n) операций size и [].

О тестах подумаю.

DrKnS

2013-04-30 19:53

administrator   bugnote:0010933

В рамках глобального рефакторинга.
Если здесь это критично по скорости - не вопрос, пусть будет вектор.

MikeMirzayanov

2013-04-30 20:10

reporter   bugnote:0010934

Думать надо, скорее ничего страшного. Я посмотрю.

Просто, кажется, это странный рефакторинг. Вроде как-раз по-дефолту лучше vector использовать из-за причин, что я описал. На StackOverflow тоже обсуждалось, вроде это общее мнение сообщества.

Еще вопрос - чем компилируете, что появлялись warnings? Я компилирую MinGW 4.7.2 вот так "build.bat gcc 32 [debug]". Может добавить "-Wextra -Wconversion" в makefile_gcc_common?

DrKnS

2013-04-30 20:42

administrator   bugnote:0010935

vc2010/2012.

-Wextra -Wconversion пока даёт слишком много лишнего, но в перспективе можно, да.

fml2

2013-05-01 18:24

reporter   bugnote:0010941

В справке по этой уникальной функции я нашел несколько опечаток. Вот они:

1. "будут (остануться одной частью).": в слове "остануться" надо убрать мягкий знак (т.е. правильно будет "останутся")

2. "...части как образца, так и результата замены не пусты...": после "части" и после "замены" я бы поставил тире

3. "набор правил форматирования под которые он подходит": после "форматирования" надо поставить запятую

DrKnS

2014-01-19 16:32

administrator   bugnote:0011722

MikeMirzayanov, проверьте пожалуйста имплементацию InternalPreserveStyleTokenize.

Там есть такое:

wchar_t Sep = 0;
...
if (Sep != 0 && strStr[I] != Sep)
{
<code>
}

Похоже, что <code> никогда не исполняется.

MikeMirzayanov

2014-01-19 20:04

reporter   bugnote:0011723

Похоже это правда. Я перечитал код, видимо это осталось от какой-то промежуточной версии. Прикладываю патч, где вырезал этот момент.

MikeMirzayanov

2014-01-19 20:04

reporter  

preservestyle.cpp.patch (790 bytes)   
Index: preservestyle.cpp
===================================================================
--- preservestyle.cpp	(revision 11364)
+++ preservestyle.cpp	(working copy)
@@ -81,23 +81,12 @@
 {
 	std::list<PreserveStyleToken> Result;
 
-	wchar_t Sep = 0;
 	std::vector<bool> Seps(Length, false);
 	for (size_t I = From+1; I+1 < From+Length; I++)
 		if (IsPreserveStyleTokenSeparator(strStr[I])
 				&& !IsPreserveStyleTokenSeparator(strStr[I-1])
 				&& !IsPreserveStyleTokenSeparator(strStr[I+1]))
 		{
-			if (Sep != 0 && strStr[I] != Sep)
-			{
-				PreserveStyleToken T;
-				T.Token = strStr.substr(From, Length);
-				T.PrependChar = 0;
-				T.TypeMask = 1 << UNKNOWN;
-				Result.emplace_back(T);
-				return Result;
-			}
-
 			Seps[I-From] = true;
 		}
 
preservestyle.cpp.patch (790 bytes)   

JohnDoe

2017-11-11 16:38

updater   bugnote:0015823

Не доделано?

fml2

2017-11-11 22:36

reporter   bugnote:0015830

Ошибки в справке тоже не исправлены (см. 0002294:0010941).

Issue History

Date Modified Username Field Change
2013-02-08 23:57 MikeMirzayanov New Issue
2013-02-08 23:57 MikeMirzayanov File Added: PreserveStyle.diff
2013-02-09 00:17 alexy Note Added: 0010235
2013-02-09 00:26 MikeMirzayanov Note Added: 0010236
2013-02-09 00:30 alexy Note Added: 0010237
2013-02-09 00:48 MikeMirzayanov Note Added: 0010238
2013-02-09 00:54 MikeMirzayanov Note Added: 0010239
2013-02-09 00:57 alexy Note Added: 0010240
2013-02-09 01:01 alexy Note Added: 0010241
2013-02-09 09:47 DrKnS Note Added: 0010242
2013-02-09 10:08 MikeMirzayanov Note Added: 0010243
2013-02-09 10:19 DrKnS Note Added: 0010244
2013-02-09 12:18 DrKnS Note Added: 0010260
2013-02-09 12:40 MikeMirzayanov Note Added: 0010261
2013-02-12 19:57 DrKnS Note Added: 0010272
2013-02-13 09:01 DAtaMan Note Added: 0010277
2013-02-14 15:28 MikeMirzayanov File Added: help.diff
2013-02-14 15:28 MikeMirzayanov Note Added: 0010293
2013-02-15 06:19 vskirdin Note Added: 0010296
2013-02-15 21:13 DrKnS Assigned To => DrKnS
2013-02-15 21:13 DrKnS Status new => assigned
2013-02-18 05:55 vskirdin Note Added: 0010312
2013-02-18 12:27 MikeMirzayanov Note Added: 0010313
2013-02-27 21:48 MikeMirzayanov File Added: PreserveStyle-Description.diff
2013-02-27 21:49 MikeMirzayanov Note Added: 0010393
2013-02-28 06:00 vskirdin Note Added: 0010394
2013-02-28 06:00 vskirdin File Added: PreserveStyle.hlf
2013-03-17 12:30 DrKnS Note Added: 0010535
2013-03-17 13:11 MikeMirzayanov Note Added: 0010536
2013-03-17 13:40 DrKnS Note Added: 0010537
2013-03-17 14:29 MikeMirzayanov Note Added: 0010538
2013-04-28 13:20 alexy Note Added: 0010913
2013-04-29 23:59 MikeMirzayanov File Added: PreserveStyle-Improvement.diff
2013-04-30 00:03 MikeMirzayanov Note Added: 0010924
2013-04-30 18:24 DrKnS Note Added: 0010928
2013-04-30 18:53 MikeMirzayanov Note Added: 0010929
2013-04-30 19:22 DrKnS Note Added: 0010930
2013-04-30 19:48 MikeMirzayanov Note Added: 0010931
2013-04-30 19:53 DrKnS Note Added: 0010933
2013-04-30 20:10 MikeMirzayanov Note Added: 0010934
2013-04-30 20:42 DrKnS Note Added: 0010935
2013-05-01 18:24 fml2 Note Added: 0010941
2014-01-19 16:32 DrKnS Note Added: 0011722
2014-01-19 20:04 MikeMirzayanov Note Added: 0011723
2014-01-19 20:04 MikeMirzayanov File Added: preservestyle.cpp.patch
2017-11-11 16:38 JohnDoe Note Added: 0015823
2017-11-11 22:36 fml2 Note Added: 0015830