Skip to content

Commit af67b0b

Browse files
committed
added nth Match logik
1 parent c37da8b commit af67b0b

File tree

3 files changed

+120
-73
lines changed

3 files changed

+120
-73
lines changed

src/MultiReplacePanel.cpp

+117-72
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ void MultiReplace::positionAndResizeControls(int windowWidth, int windowHeight)
269269
ctrlMap[IDC_MATCH_CASE_CHECKBOX] = { sx(16), sy(101), sx(158), checkboxHeight, WC_BUTTON, getLangStrLPCWSTR(L"panel_match_case"), BS_AUTOCHECKBOX | WS_TABSTOP, NULL };
270270
ctrlMap[IDC_USE_VARIABLES_CHECKBOX] = { sx(16), sy(126), sx(134), checkboxHeight, WC_BUTTON, getLangStrLPCWSTR(L"panel_use_variables"), BS_AUTOCHECKBOX | WS_TABSTOP, NULL };
271271
ctrlMap[IDC_USE_VARIABLES_HELP] = { sx(152), sy(126), sx(20), sy(20), WC_BUTTON, getLangStrLPCWSTR(L"panel_help"), BS_PUSHBUTTON | WS_TABSTOP, NULL };
272-
ctrlMap[IDC_REPLACE_FIRST_CHECKBOX] = { sx(16), sy(151), sx(158), checkboxHeight, WC_BUTTON, getLangStrLPCWSTR(L"panel_replace_first_match_only"), BS_AUTOCHECKBOX | WS_TABSTOP, NULL };
272+
ctrlMap[IDC_REPLACE_FIRST_CHECKBOX] = { sx(16), sy(151), sx(112), checkboxHeight, WC_BUTTON, getLangStrLPCWSTR(L"panel_replace_first_match_only"), BS_AUTOCHECKBOX | WS_TABSTOP, NULL };
273+
ctrlMap[IDC_REPLACE_HIT_EDIT] = { sx(130), sy(151), sx(41), sy(16), WC_EDIT, NULL, ES_LEFT | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL, NULL };
273274
ctrlMap[IDC_WRAP_AROUND_CHECKBOX] = { sx(16), sy(176), sx(158), checkboxHeight, WC_BUTTON, getLangStrLPCWSTR(L"panel_wrap_around"), BS_AUTOCHECKBOX | WS_TABSTOP, NULL };
274275

275276
ctrlMap[IDC_SEARCH_MODE_GROUP] = { sx(180), sy(79), sx(173), sy(104), WC_BUTTON, getLangStrLPCWSTR(L"panel_search_mode"), BS_GROUPBOX, NULL };
@@ -4232,14 +4233,30 @@ bool MultiReplace::replaceAll(const ReplaceItemData& itemData, int& findCount, i
42324233
context.docLength = send(SCI_GETLENGTH, 0, 0);
42334234
context.isColumnMode = (IsDlgButtonChecked(_hSelf, IDC_COLUMN_MODE_RADIO) == BST_CHECKED);
42344235
context.isSelectionMode = (IsDlgButtonChecked(_hSelf, IDC_SELECTION_RADIO) == BST_CHECKED);
4235-
context.retrieveFoundText = itemData.useVariables;;
4236+
context.retrieveFoundText = itemData.useVariables;
42364237
context.highlightMatch = false;
42374238

42384239
send(SCI_SETSEARCHFLAGS, context.searchFlags);
42394240

42404241
SearchResult searchResult = performSearchForward(context, 0);
42414242

42424243
bool isReplaceFirstEnabled = (IsDlgButtonChecked(_hSelf, IDC_REPLACE_FIRST_CHECKBOX) == BST_CHECKED);
4244+
4245+
std::vector<int> selectedMatches;
4246+
4247+
// Only process the match selection if the option is enabled
4248+
if (isReplaceFirstEnabled) {
4249+
selectedMatches = parseNumberRanges(
4250+
getTextFromDialogItem(_hSelf, IDC_REPLACE_HIT_EDIT),
4251+
getLangStr(L"status_invalid_match_selection")
4252+
);
4253+
4254+
// If the Edit field is empty, default to replacing only the first match
4255+
if (selectedMatches.empty()) {
4256+
selectedMatches.push_back(1);
4257+
}
4258+
}
4259+
42434260
int previousLineIndex = -1;
42444261
int lineFindCount = 0;
42454262

@@ -4307,30 +4324,34 @@ bool MultiReplace::replaceAll(const ReplaceItemData& itemData, int& findCount, i
43074324
}
43084325

43094326
Sci_Position newPos;
4310-
if (!skipReplace) {
4311-
if (itemData.regex) {
4312-
newPos = performRegexReplace(replaceTextUtf8, searchResult.pos, searchResult.length);
4327+
4328+
// ** Check if the current match should be replaced **
4329+
if (!isReplaceFirstEnabled || std::find(selectedMatches.begin(), selectedMatches.end(), findCount) != selectedMatches.end())
4330+
{
4331+
if (!skipReplace) {
4332+
if (itemData.regex) {
4333+
newPos = performRegexReplace(replaceTextUtf8, searchResult.pos, searchResult.length);
4334+
}
4335+
else {
4336+
newPos = performReplace(replaceTextUtf8, searchResult.pos, searchResult.length);
4337+
}
4338+
replaceCount++;
4339+
4340+
if (itemIndex != SIZE_MAX) { // check if used in List
4341+
updateCountColumns(itemIndex, -1, replaceCount);
4342+
}
4343+
4344+
context.docLength = send(SCI_GETLENGTH, 0, 0);
43134345
}
43144346
else {
4315-
newPos = performReplace(replaceTextUtf8, searchResult.pos, searchResult.length);
4347+
newPos = searchResult.pos + searchResult.length;
4348+
// Clear selection
4349+
send(SCI_SETSELECTIONSTART, newPos, 0);
4350+
send(SCI_SETSELECTIONEND, newPos, 0);
43164351
}
4317-
replaceCount++;
4318-
4319-
if (itemIndex != SIZE_MAX) { // check if used in List
4320-
updateCountColumns(itemIndex, -1, replaceCount);
4321-
}
4322-
4323-
context.docLength = send(SCI_GETLENGTH, 0, 0);
43244352
}
43254353
else {
4326-
newPos = searchResult.pos + searchResult.length;
4327-
// Clear selection
4328-
send(SCI_SETSELECTIONSTART, newPos, 0);
4329-
send(SCI_SETSELECTIONEND, newPos, 0);
4330-
}
4331-
4332-
if (isReplaceFirstEnabled) {
4333-
break; // Exit the loop after the first successful replacement
4354+
newPos = searchResult.pos + searchResult.length; // Move to next match without replacing
43344355
}
43354356

43364357
searchResult = performSearchForward(context, newPos);
@@ -6598,66 +6619,26 @@ bool MultiReplace::parseColumnAndDelimiterData() {
65986619
std::string extendedDelimiter = convertAndExtend(delimiterData, true);
65996620
std::string quoteCharConverted = wstringToString(quoteCharString);
66006621

6601-
// **Check for changes BEFORE modifying existing values**
6622+
// Check for changes BEFORE modifying existing values
66026623
bool delimiterChanged = (columnDelimiterData.extendedDelimiter != extendedDelimiter);
66036624
bool quoteCharChanged = (columnDelimiterData.quoteChar != quoteCharConverted);
66046625

6605-
// **Process column data before clearing old values**
6606-
std::set<int> parsedColumns;
6607-
std::vector<int> parsedInputColumns;
6608-
66096626
// Trim leading and trailing commas from column data
66106627
columnDataString.erase(0, columnDataString.find_first_not_of(L','));
66116628
columnDataString.erase(columnDataString.find_last_not_of(L',') + 1);
66126629

6630+
// Ensure that columnDataString and delimiter are not empty
66136631
if (columnDataString.empty() || delimiterData.empty()) {
66146632
showStatusMessage(getLangStr(L"status_missing_column_or_delimiter_data"), COLOR_ERROR);
66156633
return false;
66166634
}
66176635

6618-
// Lambda function to process a token (either a single column or a range like "1-3")
6619-
auto processToken = [&](const std::wstring& token) -> bool {
6620-
if (token.empty()) return true; // Ignore empty tokens
6621-
try {
6622-
size_t dashPos = token.find(L'-');
6623-
if (dashPos != std::wstring::npos) { // Token represents a range (e.g., "1-3")
6624-
int startRange = std::stoi(token.substr(0, dashPos));
6625-
int endRange = std::stoi(token.substr(dashPos + 1));
6626-
if (startRange < 1 || endRange < startRange) {
6627-
showStatusMessage(getLangStr(L"status_invalid_range_in_column_data"), COLOR_ERROR);
6628-
return false;
6629-
}
6630-
for (int i = startRange; i <= endRange; ++i) {
6631-
if (parsedColumns.insert(i).second) {
6632-
parsedInputColumns.push_back(i);
6633-
}
6634-
}
6635-
}
6636-
else { // Token represents a single column (e.g., "5")
6637-
int col = std::stoi(token);
6638-
if (col < 1) {
6639-
showStatusMessage(getLangStr(L"status_invalid_column_number"), COLOR_ERROR);
6640-
return false;
6641-
}
6642-
if (parsedColumns.insert(col).second) {
6643-
parsedInputColumns.push_back(col);
6644-
}
6645-
}
6646-
}
6647-
catch (const std::exception&) {
6648-
showStatusMessage(getLangStr(L"status_syntax_error_in_column_data"), COLOR_ERROR);
6649-
return false;
6650-
}
6651-
return true;
6652-
};
6636+
// Use parseNumberRanges() to process column data
6637+
std::vector<int> parsedColumns = parseNumberRanges(columnDataString, getLangStr(L"status_invalid_range_in_column_data"));
6638+
if (parsedColumns.empty()) return false; // Abort if parsing failed
66536639

6654-
// Tokenize columnDataString using a stream and process each token
6655-
std::wistringstream iss(columnDataString);
6656-
std::wstring token;
6657-
while (std::getline(iss, token, L',')) {
6658-
if (!processToken(token))
6659-
return false;
6660-
}
6640+
// Convert parsedColumns to set for uniqueness
6641+
std::set<int> uniqueColumns(parsedColumns.begin(), parsedColumns.end());
66616642

66626643
// Validate delimiter
66636644
if (extendedDelimiter.empty()) {
@@ -6672,16 +6653,16 @@ bool MultiReplace::parseColumnAndDelimiterData() {
66726653
return false;
66736654
}
66746655

6675-
// **Check if columns have changed BEFORE updating stored data**
6676-
bool columnChanged = (columnDelimiterData.columns != parsedColumns);
6656+
// Check if columns have changed BEFORE updating stored data
6657+
bool columnChanged = (columnDelimiterData.columns != uniqueColumns);
66776658

66786659
// Update columnDelimiterData values
66796660
columnDelimiterData.delimiterChanged = delimiterChanged;
66806661
columnDelimiterData.quoteCharChanged = quoteCharChanged;
66816662
columnDelimiterData.columnChanged = columnChanged;
66826663

6683-
columnDelimiterData.inputColumns = std::move(parsedInputColumns);
6684-
columnDelimiterData.columns = std::move(parsedColumns);
6664+
columnDelimiterData.inputColumns = std::move(parsedColumns);
6665+
columnDelimiterData.columns = std::move(uniqueColumns);
66856666
columnDelimiterData.extendedDelimiter = std::move(extendedDelimiter);
66866667
columnDelimiterData.delimiterLength = columnDelimiterData.extendedDelimiter.length();
66876668
columnDelimiterData.quoteChar = std::move(quoteCharConverted);
@@ -7920,6 +7901,70 @@ int MultiReplace::getFontHeight(HWND hwnd, HFONT hFont) {
79207901
return fontHeight; // Return the font height
79217902
}
79227903

7904+
std::vector<int> MultiReplace::parseNumberRanges(const std::wstring& input, const std::wstring& errorMessage)
7905+
{
7906+
std::vector<int> result;
7907+
if (input.empty()) return result; // Return empty vector if input is empty
7908+
7909+
std::set<int> uniqueNumbers;
7910+
std::wistringstream stream(input);
7911+
std::wstring token;
7912+
7913+
// Lambda function to process each token (either a single number or a range "1-5")
7914+
auto processToken = [&](const std::wstring& token) -> bool
7915+
{
7916+
if (token.empty()) return true; // Ignore empty tokens
7917+
7918+
try
7919+
{
7920+
size_t dashPos = token.find(L'-');
7921+
if (dashPos != std::wstring::npos)
7922+
{
7923+
// Extract range start and end
7924+
int startRange = std::stoi(token.substr(0, dashPos));
7925+
int endRange = std::stoi(token.substr(dashPos + 1));
7926+
7927+
// Validate range
7928+
if (startRange < 1 || endRange < startRange)
7929+
return false;
7930+
7931+
// Insert numbers in the range
7932+
for (int i = startRange; i <= endRange; ++i)
7933+
uniqueNumbers.insert(i);
7934+
}
7935+
else
7936+
{
7937+
// Single number
7938+
int number = std::stoi(token);
7939+
if (number < 1)
7940+
return false; // Invalid input
7941+
7942+
uniqueNumbers.insert(number);
7943+
}
7944+
}
7945+
catch (const std::exception&)
7946+
{
7947+
return false; // Invalid input format
7948+
}
7949+
7950+
return true;
7951+
};
7952+
7953+
// Split input by comma and process each token
7954+
while (std::getline(stream, token, L','))
7955+
{
7956+
if (!processToken(token))
7957+
{
7958+
showStatusMessage(errorMessage, COLOR_ERROR);
7959+
return {}; // Invalid input -> return empty vector
7960+
}
7961+
}
7962+
7963+
result.assign(uniqueNumbers.begin(), uniqueNumbers.end());
7964+
return result;
7965+
}
7966+
7967+
79237968
#pragma endregion
79247969

79257970

src/MultiReplacePanel.h

+1
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,7 @@ class MultiReplace : public StaticDialog
776776
std::vector<WCHAR> createFilterString(const std::vector<std::pair<std::wstring, std::wstring>>& filters);
777777
int getCharacterWidth(int elementID, const wchar_t* character);
778778
int getFontHeight(HWND hwnd, HFONT hFont);
779+
std::vector<int> MultiReplace::parseNumberRanges(const std::wstring& input, const std::wstring& errorMessage);
779780

780781
//StringHandling
781782
std::wstring stringToWString(const std::string& encodedInput) const;

src/StaticDialog/resource.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
#define IDC_USE_VARIABLES_CHECKBOX 5202
6969
#define IDC_USE_VARIABLES_HELP 5203
7070
#define IDC_REPLACE_FIRST_CHECKBOX 5204
71-
#define IDC_WRAP_AROUND_CHECKBOX 5205
71+
#define IDC_REPLACE_HIT_EDIT 5205
72+
#define IDC_WRAP_AROUND_CHECKBOX 5206
7273

7374
#define IDC_SEARCH_MODE_GROUP 5300
7475
#define IDC_NORMAL_RADIO 5301

0 commit comments

Comments
 (0)