From 08128ee36a31fdb0b3d72d1d7342f50e8103ea47 Mon Sep 17 00:00:00 2001 From: Don Ho Date: Fri, 17 Jun 2022 05:45:33 +0200 Subject: [PATCH] Add an option allows to show only 1 entry per found line in search result Also mark several found occurrences in the same entry - user can click on the marked occurrence to jump among found result in the found line. This option is enabled by default. It can be disabled in "Searching" section of Preferences dialog. It's an alternative implementation of #11705 Fix #2481, fix #1499, fix #5547, fix #2987, fix #4525, fix #3224, close #11808 --- PowerEditor/src/Parameters.cpp | 12 +- PowerEditor/src/Parameters.h | 1 + .../src/ScintillaComponent/FindReplaceDlg.cpp | 456 +++++++++++++++--- .../src/ScintillaComponent/FindReplaceDlg.h | 35 +- .../src/WinControls/Preference/preference.rc | 1 + .../WinControls/Preference/preferenceDlg.cpp | 8 + .../WinControls/Preference/preference_rc.h | 1 + lexilla/lexers/LexSearchResult.cxx | 36 +- scintilla/include/Scintilla.h | 8 +- 9 files changed, 456 insertions(+), 102 deletions(-) diff --git a/PowerEditor/src/Parameters.cpp b/PowerEditor/src/Parameters.cpp index 61cab8e00..3e0871f35 100644 --- a/PowerEditor/src/Parameters.cpp +++ b/PowerEditor/src/Parameters.cpp @@ -4857,11 +4857,18 @@ void NppParameters::feedGUIParameters(TiXmlNode *node) { _nppGUI._finderLinesAreCurrentlyWrapped = (!lstrcmp(val, TEXT("yes"))); } + val = element->Attribute(TEXT("purgeBeforeEverySearch")); if (val) { _nppGUI._finderPurgeBeforeEverySearch = (!lstrcmp(val, TEXT("yes"))); } + + val = element->Attribute(TEXT("showOnlyOneEntryPerFoundLine")); + if (val) + { + _nppGUI._finderShowOnlyOneEntryPerFoundLine = (!lstrcmp(val, TEXT("yes"))); + } } else if (!lstrcmp(nm, TEXT("NewDocDefaultSettings"))) @@ -6319,7 +6326,7 @@ void NppParameters::createXmlTreeFromGUIParams() GUIConfigElement->SetAttribute(TEXT("bottom"), _nppGUI._findWindowPos.bottom); } - // + // { TiXmlElement* GUIConfigElement = (newGUIRoot->InsertEndChild(TiXmlElement(TEXT("GUIConfig"))))->ToElement(); GUIConfigElement->SetAttribute(TEXT("name"), TEXT("FinderConfig")); @@ -6327,6 +6334,9 @@ void NppParameters::createXmlTreeFromGUIParams() GUIConfigElement->SetAttribute(TEXT("wrappedLines"), pStr); pStr = _nppGUI._finderPurgeBeforeEverySearch ? TEXT("yes") : TEXT("no"); GUIConfigElement->SetAttribute(TEXT("purgeBeforeEverySearch"), pStr); + pStr = _nppGUI._finderShowOnlyOneEntryPerFoundLine ? TEXT("yes") : TEXT("no"); + GUIConfigElement->SetAttribute(TEXT("showOnlyOneEntryPerFoundLine"), pStr); + } // no diff --git a/PowerEditor/src/Parameters.h b/PowerEditor/src/Parameters.h index 22988dca3..43c2c408f 100644 --- a/PowerEditor/src/Parameters.h +++ b/PowerEditor/src/Parameters.h @@ -764,6 +764,7 @@ struct NppGUI final bool _finderLinesAreCurrentlyWrapped = false; bool _finderPurgeBeforeEverySearch = false; + bool _finderShowOnlyOneEntryPerFoundLine = true; int _fileAutoDetection = cdEnabledNew; diff --git a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp index e2ac64894..9e2bd850d 100644 --- a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp +++ b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.cpp @@ -474,7 +474,7 @@ void FindReplaceDlg::updateCombo(int comboID) } FoundInfo Finder::EmptyFoundInfo(0, 0, 0, TEXT("")); -SearchResultMarking Finder::EmptySearchResultMarking; +SearchResultMarkingLine Finder::EmptySearchResultMarking; bool Finder::notify(SCNotification *notification) { @@ -503,7 +503,18 @@ bool Finder::notify(SCNotification *notification) pos = _scintView.execute(SCI_GETLINEENDPOSITION, notification->line); _scintView.execute(SCI_SETSEL, pos, pos); - gotoFoundLine(); + std::pair newPos = gotoFoundLine(); + auto lineStartAbsPos = _scintView.execute(SCI_POSITIONFROMLINE, notification->line); + intptr_t lineEndAbsPos = _scintView.execute(SCI_GETLINEENDPOSITION, notification->line); + + intptr_t begin = newPos.first + lineStartAbsPos; + intptr_t end = newPos.second + lineStartAbsPos; + + if (end > lineEndAbsPos) + end = lineEndAbsPos; + + _scintView.execute(SCI_SETSEL, begin, end); + } break; @@ -519,28 +530,55 @@ bool Finder::notify(SCNotification *notification) } -void Finder::gotoFoundLine() +std::pair Finder::gotoFoundLine(size_t nOccurrence) { + std::pair emptyResult(0, 0); auto currentPos = _scintView.execute(SCI_GETCURRENTPOS); auto lno = _scintView.execute(SCI_LINEFROMPOSITION, currentPos); auto start = _scintView.execute(SCI_POSITIONFROMLINE, lno); auto end = _scintView.execute(SCI_GETLINEENDPOSITION, lno); - if (start + 2 >= end) return; // avoid empty lines + if (start + 2 >= end) return emptyResult; // avoid empty lines if (_scintView.execute(SCI_GETFOLDLEVEL, lno) & SC_FOLDLEVELHEADERFLAG) { _scintView.execute(SCI_TOGGLEFOLD, lno); - return; + return emptyResult; } - const FoundInfo fInfo = *(_pMainFoundInfos->begin() + lno); + const FoundInfo& fInfo = *(_pMainFoundInfos->begin() + lno); + const SearchResultMarkingLine& markingLine = *(_pMainMarkings->begin() + lno); // Switch to another document - if (!::SendMessage(_hParent, WM_DOOPEN, 0, reinterpret_cast(fInfo._fullPath.c_str()))) return; + if (!::SendMessage(_hParent, WM_DOOPEN, 0, reinterpret_cast(fInfo._fullPath.c_str()))) return emptyResult; (*_ppEditView)->_positionRestoreNeeded = false; - Searching::displaySectionCentered(fInfo._start, fInfo._end, *_ppEditView); + + size_t index = 0; + + if (nOccurrence > 0) + { + index = nOccurrence - 1; + } + else // nOccurrence not used: use current line relative pos to check if it's inside of a marked occurrence + { + intptr_t currentPosInLine = currentPos - start; + + for (std::pair range : markingLine._segmentPostions) + { + if (range.first <= currentPosInLine && currentPosInLine <= range.second) + break; + + ++index; + } + } + + if (index >= fInfo._ranges.size()) + index = 0; + + Searching::displaySectionCentered(fInfo._ranges[index].first, fInfo._ranges[index].second, *_ppEditView); + + return markingLine._segmentPostions[index]; } void Finder::deleteResult() @@ -620,15 +658,123 @@ bool Finder::canFind(const TCHAR *fileName, size_t lineNumber) const return false; } +// Y : current pos +// X : current sel +// XXXXY : current sel + current pos +// +// 1 2 3 4 Status auxiliaryInfo +// ======================================================================================= +// Y [ ] [ ] [ ] [ ] : pos_infront -1 +// [ ] [ ] [ ] [ Y ] : pos_inside 4 +// [ ] XXY ] [ ] [ ] : pos_inside 2 +// [ ] [ ] [ ] [ XXXY : pos_inside 4 +// [ ] [ ] [XXXXXY [ ] : pos_inside 3 +// [ Y [ ] [ ] [ ] : pos_between 1 +// [ ] [ ] [ ] Y ] : pos_between 3 +// [ ] [ ] [ Y [ ] : pos_between 3 +// [ ] [ ] Y [ ] [ ] : pos_between 2 +// [ ] [ ] [ ] [ ] Y : pos_behind 4 + +Finder::CurrentPosInLineInfo Finder::getCurrentPosInLineInfo(intptr_t currentPosInLine, const SearchResultMarkingLine& markingLine) const +{ + CurrentPosInLineInfo cpili; + size_t count = 0; + intptr_t lastEnd = 0; + auto selStart = _scintView.execute(SCI_GETSELECTIONSTART); + auto selEnd = _scintView.execute(SCI_GETSELECTIONEND); + bool hasSel = (selEnd - selStart) != 0; + + for (std::pair range : markingLine._segmentPostions) + { + ++count; + + if (lastEnd <= currentPosInLine && currentPosInLine < range.first) + { + if (count == 1) + { + cpili._status = pos_infront; + break; + } + else + { + cpili._status = pos_between; + cpili.auxiliaryInfo = count - 1; + break; + } + } + + if (range.first <= currentPosInLine && currentPosInLine <= range.second) + { + if (currentPosInLine == range.first && !hasSel) + { + cpili._status = pos_between; + cpili.auxiliaryInfo = count - 1; // c1 c2 + // [ ] I[ ] : I is recongnized with c2, so auxiliaryInfo should be c1 (c2-1) + } + else if (currentPosInLine == range.second && !hasSel) + { + cpili._status = pos_between; + cpili.auxiliaryInfo = count; // c1 c2 + // [ ]I [ ] : I is recongnized with c1, so auxiliaryInfo should be c1 + } + else + { + cpili._status = pos_inside; + cpili.auxiliaryInfo = count; + } + break; + } + + if (range.second < currentPosInLine) + { + if (markingLine._segmentPostions.size() == count) + { + cpili._status = pos_behind; + cpili.auxiliaryInfo = count; + break; + } + } + + lastEnd = range.second; + } + + return cpili; +} + +void Finder::anchorWithNoHeaderLines(intptr_t& currentL, intptr_t initL, intptr_t minL, intptr_t maxL, int direction) +{ + if (currentL > maxL && direction == 0) + currentL = minL; + + while (_scintView.execute(SCI_GETFOLDLEVEL, currentL) & SC_FOLDLEVELHEADERFLAG) + { + currentL += direction == -1 ? -1 : 1; + + if (currentL > maxL) + currentL = minL; + else if (currentL < minL) + currentL = maxL; + + if (currentL == initL) + break; + } + + auto extremityAbsoltePos = _scintView.execute(direction == -1 ? SCI_GETLINEENDPOSITION : SCI_POSITIONFROMLINE, currentL); + _scintView.execute(SCI_SETSEL, extremityAbsoltePos, extremityAbsoltePos); +} + void Finder::gotoNextFoundResult(int direction) { - int increment = direction < 0 ? -1 : 1; + // + // Get currentLine & currentPosInLine from CurrentPos + // auto currentPos = _scintView.execute(SCI_GETCURRENTPOS); - auto lno = _scintView.execute(SCI_LINEFROMPOSITION, currentPos); + intptr_t lno = _scintView.execute(SCI_LINEFROMPOSITION, currentPos); auto total_lines = _scintView.execute(SCI_GETLINECOUNT); if (total_lines <= 1) return; - if (lno == total_lines - 1) lno--; // last line doesn't belong to any search, use last search + auto lineStartAbsPos = _scintView.execute(SCI_POSITIONFROMLINE, lno); + intptr_t currentPosInLine = currentPos - lineStartAbsPos; auto init_lno = lno; auto max_lno = _scintView.execute(SCI_GETLASTCHILD, lno, searchHeaderLevel); @@ -648,28 +794,138 @@ void Finder::gotoNextFoundResult(int direction) assert(min_lno <= max_lno); - lno += increment; - - if (lno > max_lno) lno = min_lno; + if (lno > max_lno && direction == 0) lno = min_lno; else if (lno < min_lno) lno = max_lno; + // + // Set anchor and make sure that achor is not on the last (empty) line or head lines + // while (_scintView.execute(SCI_GETFOLDLEVEL, lno) & SC_FOLDLEVELHEADERFLAG) { - lno += increment; - if (lno > max_lno) lno = min_lno; - else if (lno < min_lno) lno = max_lno; - if (lno == init_lno) break; + lno += direction == -1 ? -1 : 1; + + if (lno > max_lno) + lno = min_lno; + else if (lno < min_lno) + lno = max_lno; + + if (lno == init_lno) + break; } - if ((_scintView.execute(SCI_GETFOLDLEVEL, lno) & SC_FOLDLEVELHEADERFLAG) == 0) + if (lno != init_lno) { + auto extremityAbsoltePos = _scintView.execute(direction == -1 ? SCI_GETLINEENDPOSITION : SCI_POSITIONFROMLINE, lno); + _scintView.execute(SCI_SETSEL, extremityAbsoltePos, extremityAbsoltePos); + currentPos = extremityAbsoltePos; auto start = _scintView.execute(SCI_POSITIONFROMLINE, lno); - _scintView.execute(SCI_SETSEL, start, start); - _scintView.execute(SCI_ENSUREVISIBLE, lno); - _scintView.execute(SCI_SCROLLCARET); - - gotoFoundLine(); + currentPosInLine = currentPos - start; } + + + size_t n = 0; + const SearchResultMarkingLine& markingLine = *(_pMainMarkings->begin() + lno); + + // + // Determinate currentPosInLine status among pos_infront, pose_between, pos_inside and pos_behind + // + CurrentPosInLineInfo cpili = getCurrentPosInLineInfo(currentPosInLine, markingLine); + + // + // According CurrentPosInLineInfo and direction, set position and get number of occurrence + // + if (direction == 0) // Next + { + switch (cpili._status) + { + case pos_infront: + { + n = 1; + } + break; + + case pos_between: + case pos_inside: + { + n = cpili.auxiliaryInfo + 1; + if (n > markingLine._segmentPostions.size()) + { + lno++; + anchorWithNoHeaderLines(lno, init_lno, min_lno, max_lno, direction); + n = 1; + } + } + break; + + case pos_behind: + { + lno++; + anchorWithNoHeaderLines(lno, init_lno, min_lno, max_lno, direction); + n = 1; + } + break; + } + } + else if (direction == -1) // Previous + { + switch (cpili._status) + { + case pos_infront: + { + lno--; + anchorWithNoHeaderLines(lno, init_lno, min_lno, max_lno, direction); + const SearchResultMarkingLine& newMarkingLine = *(_pMainMarkings->begin() + lno); + n = newMarkingLine._segmentPostions.size(); + } + break; + + case pos_between: + { + n = cpili.auxiliaryInfo; + } + break; + + case pos_inside: + { + if (cpili.auxiliaryInfo > 1) + n = cpili.auxiliaryInfo - 1; + else + { + lno--; + anchorWithNoHeaderLines(lno, init_lno, min_lno, max_lno, direction); + const SearchResultMarkingLine& newMarkingLine = *(_pMainMarkings->begin() + lno); + n = newMarkingLine._segmentPostions.size(); + } + } + break; + + case pos_behind: + { + n = cpili.auxiliaryInfo; + } + break; + } + } + else // invalid + { + return; + } + + _scintView.execute(SCI_ENSUREVISIBLE, lno); + _scintView.execute(SCI_SCROLLCARET); + std::pair newPos = gotoFoundLine(n); + + lineStartAbsPos = _scintView.execute(SCI_POSITIONFROMLINE, lno); + intptr_t lineEndAbsPos = _scintView.execute(SCI_GETLINEENDPOSITION, lno); + + intptr_t begin = newPos.first + lineStartAbsPos; + intptr_t end = newPos.second + lineStartAbsPos; + + if (end > lineEndAbsPos) + end = lineEndAbsPos; + + _scintView.execute(SCI_SETSEL, begin, end); + } void FindInFinderDlg::initFromOptions() @@ -2308,10 +2564,10 @@ int FindReplaceDlg::processRange(ProcessOperation op, FindReplaceInfo & findRepl int nbProcessed = 0; if (!isCreated() && !findReplaceInfo._txt2find) - return nbProcessed; + return 0; if (!_ppEditView) - return nbProcessed; + return 0; ScintillaEditView *pEditView = *_ppEditView; @@ -2324,7 +2580,7 @@ int FindReplaceDlg::processRange(ProcessOperation op, FindReplaceInfo & findRepl if (findReplaceInfo._startRange == findReplaceInfo._endRange) return nbProcessed; - const FindOption *pOptions = opt?opt:_env; + const FindOption *pOptions = opt ? opt : _env; LRESULT stringSizeFind = 0; LRESULT stringSizeReplace = 0; @@ -2457,10 +2713,11 @@ int FindReplaceDlg::processRange(ProcessOperation op, FindReplaceInfo & findRepl generic_string line = lineBuf; line += TEXT("\r\n"); - SearchResultMarking srm; - srm._start = static_cast(start_mark); - srm._end = static_cast(end_mark); - _pFinder->add(FoundInfo(targetStart, targetEnd, lineNumber + 1, pFileName), srm, line.c_str(), totalLineNumber); + + SearchResultMarkingLine srml; + srml._segmentPostions.push_back(std::pair(start_mark, end_mark)); + _pFinder->add(FoundInfo(targetStart, targetEnd, lineNumber + 1, pFileName), srml, line.c_str(), totalLineNumber); + break; } @@ -2490,9 +2747,9 @@ int FindReplaceDlg::processRange(ProcessOperation op, FindReplaceInfo & findRepl generic_string line = lineBuf; line += TEXT("\r\n"); - SearchResultMarking srm; - srm._start = static_cast(start_mark); - srm._end = static_cast(end_mark); + SearchResultMarkingLine srml; + srml._segmentPostions.push_back(std::pair(start_mark, end_mark)); + processed = (!pOptions->_isMatchLineNumber) || (pFindersInfo->_pSourceFinder->canFind(pFileName, lineNumber + 1)); if (processed) { @@ -2501,7 +2758,7 @@ int FindReplaceDlg::processRange(ProcessOperation op, FindReplaceInfo & findRepl pFindersInfo->_pDestFinder->addFileNameTitle(pFileName); findAllFileNameAdded = true; } - pFindersInfo->_pDestFinder->add(FoundInfo(targetStart, targetEnd, lineNumber + 1, pFileName), srm, line.c_str(), totalLineNumber); + pFindersInfo->_pDestFinder->add(FoundInfo(targetStart, targetEnd, lineNumber + 1, pFileName), srml, line.c_str(), totalLineNumber); } break; } @@ -2676,7 +2933,7 @@ void FindReplaceDlg::findAllIn(InWhat op) _pFinder->_scintView.execute(SCI_SETCODEPAGE, SC_CP_UTF8); _pFinder->_scintView.execute(SCI_USEPOPUP, FALSE); _pFinder->_scintView.execute(SCI_SETUNDOCOLLECTION, false); //dont store any undo information - _pFinder->_scintView.execute(SCI_SETCARETWIDTH, 0); + _pFinder->_scintView.execute(SCI_SETCARETWIDTH, 1); _pFinder->_scintView.showMargin(ScintillaEditView::_SC_MARGE_FOLDER, true); _pFinder->_scintView.execute(SCI_SETUSETABS, true); @@ -2716,7 +2973,7 @@ void FindReplaceDlg::findAllIn(InWhat op) if (justCreated) { // Send the address of _MarkingsStruct to the lexer - char ptrword[sizeof(void*)*2+1]; + char ptrword[sizeof(void*) * 2 + 1]; sprintf(ptrword, "%p", &_pFinder->_markingsStruct); _pFinder->_scintView.execute(SCI_SETPROPERTY, reinterpret_cast("@MarkingsStruct"), reinterpret_cast(ptrword)); @@ -2726,6 +2983,12 @@ void FindReplaceDlg::findAllIn(InWhat op) ::SendMessage(_pFinder->getHSelf(), WM_SIZE, 0, 0); + bool toRTL = (*_ppEditView)->isTextDirectionRTL(); + bool isRTL = _pFinder->_scintView.isTextDirectionRTL(); + + if ((toRTL && !isRTL) || (!toRTL && isRTL)) + _pFinder->_scintView.changeTextDirection(toRTL); + int cmdid = 0; if (op == ALL_OPEN_DOCS) cmdid = WM_FINDALL_INOPENEDDOC; @@ -2744,12 +3007,6 @@ void FindReplaceDlg::findAllIn(InWhat op) generic_string text = _pFinder->getHitsString(_findAllResult); wsprintf(_findAllResultStr, text.c_str()); - bool toRTL = (*_ppEditView)->isTextDirectionRTL(); - bool isRTL = _pFinder->_scintView.isTextDirectionRTL(); - - if ((toRTL && !isRTL) || (!toRTL && isRTL)) - _pFinder->_scintView.changeTextDirection(toRTL); - if (_findAllResult) { focusOnFinder(); @@ -2806,7 +3063,7 @@ Finder * FindReplaceDlg::createFinder() pFinder->_scintView.execute(SCI_SETCODEPAGE, SC_CP_UTF8); pFinder->_scintView.execute(SCI_USEPOPUP, FALSE); pFinder->_scintView.execute(SCI_SETUNDOCOLLECTION, false); //dont store any undo information - pFinder->_scintView.execute(SCI_SETCARETWIDTH, 0); + pFinder->_scintView.execute(SCI_SETCARETWIDTH, 1); pFinder->_scintView.showMargin(ScintillaEditView::_SC_MARGE_FOLDER, true); pFinder->_scintView.execute(SCI_SETUSETABS, true); @@ -3631,7 +3888,22 @@ LRESULT FAR PASCAL FindReplaceDlg::finderProc(HWND hwnd, UINT message, WPARAM wP ScintillaEditView *pScint = (ScintillaEditView *)(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); Finder *pFinder = (Finder *)(::GetWindowLongPtr(pScint->getHParent(), GWLP_USERDATA)); if (wParam == VK_RETURN) - pFinder->gotoFoundLine(); + { + std::pair newPos = pFinder->gotoFoundLine(); + + auto currentPos = pFinder->_scintView.execute(SCI_GETCURRENTPOS); + intptr_t lno = pFinder->_scintView.execute(SCI_LINEFROMPOSITION, currentPos); + intptr_t lineStartAbsPos = pFinder->_scintView.execute(SCI_POSITIONFROMLINE, lno); + intptr_t lineEndAbsPos = pFinder->_scintView.execute(SCI_GETLINEENDPOSITION, lno); + + intptr_t begin = newPos.first + lineStartAbsPos; + intptr_t end = newPos.second + lineStartAbsPos; + + if (end > lineEndAbsPos) + end = lineEndAbsPos; + + pFinder->_scintView.execute(SCI_SETSEL, begin, end); + } else if (wParam == VK_ESCAPE) pFinder->display(false); else // VK_DELETE @@ -3998,6 +4270,7 @@ void Finder::addSearchLine(const TCHAR *searchName) _pMainFoundInfos->push_back(EmptyFoundInfo); _pMainMarkings->push_back(EmptySearchResultMarking); + _previousLineNumber = -1; } void Finder::addFileNameTitle(const TCHAR * fileName) @@ -4013,6 +4286,7 @@ void Finder::addFileNameTitle(const TCHAR * fileName) _pMainFoundInfos->push_back(EmptyFoundInfo); _pMainMarkings->push_back(EmptySearchResultMarking); + _previousLineNumber = -1; } void Finder::addFileHitCount(int count) @@ -4075,13 +4349,34 @@ void Finder::addSearchHitCount(int count, int countSearched, bool isMatchLines, setFinderReadOnly(true); } -void Finder::add(FoundInfo fi, SearchResultMarking mi, const TCHAR* foundline, size_t totalLineNumber) +void Finder::add(FoundInfo fi, SearchResultMarkingLine miLine, const TCHAR* foundline, size_t totalLineNumber) { - _pMainFoundInfos->push_back(fi); + bool isRepeatedLine = false; - generic_string str = TEXT("\t"); - str += _prefixLineStr; - str += TEXT(" "); + NppParameters& nppParam = NppParameters::getInstance(); + NppGUI& nppGUI = nppParam.getNppGUI(); + + bool isRTL = _scintView.isTextDirectionRTL(); + + if (nppGUI._finderShowOnlyOneEntryPerFoundLine && !isRTL) // several occurrence colourizing in Search result doesn't support for RTL mode + { + if (_previousLineNumber == -1) + { + _previousLineNumber = fi._lineNumber; + } + else if (_previousLineNumber == static_cast(fi._lineNumber)) + { + isRepeatedLine = true; + } + else // previousLine != fi._lineNumber + { + _previousLineNumber = fi._lineNumber; + } + } + + generic_string headerStr = TEXT("\t"); + headerStr += _prefixLineStr; + headerStr += TEXT(" "); size_t totalLineNumberDigit = static_cast(nbDigitsFromNbLines(totalLineNumber) + 1); size_t currentLineNumberDigit = static_cast(nbDigitsFromNbLines(fi._lineNumber) + 1); @@ -4089,33 +4384,46 @@ void Finder::add(FoundInfo fi, SearchResultMarking mi, const TCHAR* foundline, s generic_string lineNumberStr = TEXT(""); lineNumberStr.append(totalLineNumberDigit - currentLineNumberDigit, ' '); lineNumberStr.append(std::to_wstring(fi._lineNumber)); - str += lineNumberStr; - str += TEXT(": "); - mi._start += str.length(); - mi._end += str.length(); - str += foundline; + headerStr += lineNumberStr; + headerStr += TEXT(": "); - WcharMbcsConvertor& wmc = WcharMbcsConvertor::getInstance(); - const char *text2AddUtf8 = wmc.wchar2char(str.c_str(), SC_CP_UTF8, &mi._start, &mi._end); // certainly utf8 here - size_t len = strlen(text2AddUtf8); + miLine._segmentPostions[0].first += headerStr.length(); + miLine._segmentPostions[0].second += headerStr.length(); - if (len >= SC_SEARCHRESULT_LINEBUFFERMAXLENGTH) + if (isRepeatedLine) // if current line is the repeated line of previous one, and settings make per found line show once in the result even there are several found occurences in the same line { - const char * endOfLongLine = " ...\r\n"; // perfectly Utf8-encoded already - size_t lenEndOfLongLine = strlen(endOfLongLine); - size_t cut = SC_SEARCHRESULT_LINEBUFFERMAXLENGTH - lenEndOfLongLine - 1; - - while ((cut > 0) && (!Utf8::isValid(& text2AddUtf8 [cut], (int)(len - cut)))) - cut--; - - memcpy ((void*) & text2AddUtf8 [cut], endOfLongLine, lenEndOfLongLine + 1); - len = cut + lenEndOfLongLine; + // Add start and end markers into the previous line's info for colourizing + _pMainMarkings->back()._segmentPostions.push_back(std::pair(miLine._segmentPostions[0].first, miLine._segmentPostions[0].second)); + _pMainFoundInfos->back()._ranges.push_back(fi._ranges.back()); } + else // default mode: allow same found line has several entries in search result if the searched occurrence is matched several times in the same line + { + _pMainFoundInfos->push_back(fi); - setFinderReadOnly(false); - _scintView.execute(SCI_ADDTEXT, len, reinterpret_cast(text2AddUtf8)); - setFinderReadOnly(true); - _pMainMarkings->push_back(mi); + headerStr += foundline; + + WcharMbcsConvertor& wmc = WcharMbcsConvertor::getInstance(); + const char* text2AddUtf8 = wmc.wchar2char(headerStr.c_str(), SC_CP_UTF8, &miLine._segmentPostions[0].first, &miLine._segmentPostions[0].second); // certainly utf8 here + size_t len = strlen(text2AddUtf8); + + if (len >= SC_SEARCHRESULT_LINEBUFFERMAXLENGTH) + { + const char* endOfLongLine = " ...\r\n"; // perfectly Utf8-encoded already + size_t lenEndOfLongLine = strlen(endOfLongLine); + size_t cut = SC_SEARCHRESULT_LINEBUFFERMAXLENGTH - lenEndOfLongLine - 1; + + while ((cut > 0) && (!Utf8::isValid(&text2AddUtf8[cut], (int)(len - cut)))) + cut--; + + memcpy((void*)&text2AddUtf8[cut], endOfLongLine, lenEndOfLongLine + 1); + len = cut + lenEndOfLongLine; + } + + setFinderReadOnly(false); + _scintView.execute(SCI_ADDTEXT, len, reinterpret_cast(text2AddUtf8)); + setFinderReadOnly(true); + _pMainMarkings->push_back(miLine); + } } void Finder::removeAll() @@ -4277,7 +4585,7 @@ void Finder::beginNewFilesSearch() void Finder::finishFilesSearch(int count, int searchedCount, bool isMatchLines, bool searchedEntireNotSelection) { std::vector* _pOldFoundInfos; - std::vector* _pOldMarkings; + std::vector* _pOldMarkings; _pOldFoundInfos = _pMainFoundInfos == &_foundInfos1 ? &_foundInfos2 : &_foundInfos1; _pOldMarkings = _pMainMarkings == &_markings1 ? &_markings2 : &_markings1; @@ -4302,6 +4610,8 @@ void Finder::finishFilesSearch(int count, int searchedCount, bool isMatchLines, //previous code: _scintView.execute(SCI_SETILEXER, 0, reinterpret_cast(CreateLexer("searchResult"))); _scintView.execute(SCI_SETPROPERTY, reinterpret_cast("fold"), reinterpret_cast("1")); + + _previousLineNumber = -1; } diff --git a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.h b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.h index 199b78e41..0a945ce2d 100644 --- a/PowerEditor/src/ScintillaComponent/FindReplaceDlg.h +++ b/PowerEditor/src/ScintillaComponent/FindReplaceDlg.h @@ -40,10 +40,11 @@ enum InWhat{ALL_OPEN_DOCS, FILES_IN_DIR, CURRENT_DOC, CURR_DOC_SELECTION, FILES_ struct FoundInfo { FoundInfo(intptr_t start, intptr_t end, size_t lineNumber, const TCHAR *fullPath) - : _start(start), _end(end), _lineNumber(lineNumber), _fullPath(fullPath) {}; - intptr_t _start; - intptr_t _end; - size_t _lineNumber; + : _lineNumber(lineNumber), _fullPath(fullPath) { + _ranges.push_back(std::pair(start, end)); + }; + std::vector> _ranges; + size_t _lineNumber = 0; generic_string _fullPath; }; @@ -102,6 +103,7 @@ private: class Finder : public DockingDlgInterface { friend class FindReplaceDlg; public: + Finder() : DockingDlgInterface(IDD_FINDRESULT) { _markingsStruct._length = 0; _markingsStruct._markings = NULL; @@ -119,7 +121,7 @@ public: void addFileNameTitle(const TCHAR * fileName); void addFileHitCount(int count); void addSearchHitCount(int count, int countSearched, bool isMatchLines, bool searchedEntireNotSelection); - void add(FoundInfo fi, SearchResultMarking mi, const TCHAR* foundline, size_t totalLineNumber); + void add(FoundInfo fi, SearchResultMarkingLine mi, const TCHAR* foundline, size_t totalLineNumber); void setFinderStyle(); void removeAll(); void openAll(); @@ -129,8 +131,9 @@ public: void copyPathnames(); void beginNewFilesSearch(); void finishFilesSearch(int count, int searchedCount, bool isMatchLines, bool searchedEntireNotSelection); + void gotoNextFoundResult(int direction); - void gotoFoundLine(); + std::pair gotoFoundLine(size_t nOccurrence = 0); // value 0 means this argument is not used void deleteResult(); std::vector getResultFilePaths() const; bool canFind(const TCHAR *fileName, size_t lineNumber) const; @@ -142,17 +145,24 @@ protected : bool notify(SCNotification *notification); private: - enum { searchHeaderLevel = SC_FOLDLEVELBASE, fileHeaderLevel, resultLevel }; + enum CurrentPosInLineStatus { pos_infront, pos_between, pos_inside, pos_behind }; + + struct CurrentPosInLineInfo { + CurrentPosInLineStatus _status; + intptr_t auxiliaryInfo = -1; // according the status + }; + ScintillaEditView **_ppEditView = nullptr; std::vector _foundInfos1; std::vector _foundInfos2; std::vector* _pMainFoundInfos = &_foundInfos1; - std::vector _markings1; - std::vector _markings2; - std::vector* _pMainMarkings = &_markings1; + std::vector _markings1; + std::vector _markings2; + std::vector* _pMainMarkings = &_markings1; SearchResultMarkings _markingsStruct; + intptr_t _previousLineNumber = -1; ScintillaEditView _scintView; unsigned int _nbFoundFiles = 0; @@ -174,7 +184,10 @@ private: generic_string & prepareStringForClipboard(generic_string & s) const; static FoundInfo EmptyFoundInfo; - static SearchResultMarking EmptySearchResultMarking; + static SearchResultMarkingLine EmptySearchResultMarking; + + CurrentPosInLineInfo getCurrentPosInLineInfo(intptr_t currentPosInLine, const SearchResultMarkingLine& markingLine) const; + void anchorWithNoHeaderLines(intptr_t& currentL, intptr_t initL, intptr_t minL, intptr_t maxL, int direction); }; diff --git a/PowerEditor/src/WinControls/Preference/preference.rc b/PowerEditor/src/WinControls/Preference/preference.rc index 6a9121f97..c87e32c7e 100644 --- a/PowerEditor/src/WinControls/Preference/preference.rc +++ b/PowerEditor/src/WinControls/Preference/preference.rc @@ -334,6 +334,7 @@ BEGIN CONTROL "Find dialog remains open after search that outputs to results window",IDC_CHECK_FINDDLG_ALWAYS_VISIBLE, "Button", BS_AUTOCHECKBOX | WS_TABSTOP,37,40,350,10 CONTROL "Confirm Replace All in All Opened Documents",IDC_CHECK_CONFIRMREPLOPENDOCS, "Button", BS_AUTOCHECKBOX | WS_TABSTOP,37,55,350,10 CONTROL "Replace: Don't move to the following occurrence", IDC_CHECK_REPLACEANDSTOP, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 37, 70, 350, 10 + CONTROL "Search Result window: show only one entry per found line (not applied to RTL mode)", IDC_CHECK_SHOWONCEPERFOUNDLINE, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 37, 85, 350, 10 END diff --git a/PowerEditor/src/WinControls/Preference/preferenceDlg.cpp b/PowerEditor/src/WinControls/Preference/preferenceDlg.cpp index 28a158672..92a8ffef5 100644 --- a/PowerEditor/src/WinControls/Preference/preferenceDlg.cpp +++ b/PowerEditor/src/WinControls/Preference/preferenceDlg.cpp @@ -4931,6 +4931,7 @@ intptr_t CALLBACK SearchingSubDlg::run_dlgProc(UINT message, WPARAM wParam, LPAR ::SendDlgItemMessage(_hSelf, IDC_CHECK_FINDDLG_ALWAYS_VISIBLE, BM_SETCHECK, nppGUI._findDlgAlwaysVisible, 0); ::SendDlgItemMessage(_hSelf, IDC_CHECK_CONFIRMREPLOPENDOCS, BM_SETCHECK, nppGUI._confirmReplaceInAllOpenDocs, 0); ::SendDlgItemMessage(_hSelf, IDC_CHECK_REPLACEANDSTOP, BM_SETCHECK, nppGUI._replaceStopsWithoutFindingNext, 0); + ::SendDlgItemMessage(_hSelf, IDC_CHECK_SHOWONCEPERFOUNDLINE, BM_SETCHECK, nppGUI._finderShowOnlyOneEntryPerFoundLine, 0); } break; @@ -4992,6 +4993,13 @@ intptr_t CALLBACK SearchingSubDlg::run_dlgProc(UINT message, WPARAM wParam, LPAR } break; + case IDC_CHECK_SHOWONCEPERFOUNDLINE: + { + nppGUI._finderShowOnlyOneEntryPerFoundLine = isCheckedOrNot(IDC_CHECK_SHOWONCEPERFOUNDLINE); + return TRUE; + } + break; + default: return FALSE; } diff --git a/PowerEditor/src/WinControls/Preference/preference_rc.h b/PowerEditor/src/WinControls/Preference/preference_rc.h index 3bbd65c05..71d212898 100644 --- a/PowerEditor/src/WinControls/Preference/preference_rc.h +++ b/PowerEditor/src/WinControls/Preference/preference_rc.h @@ -402,6 +402,7 @@ #define IDC_CHECK_FINDDLG_ALWAYS_VISIBLE (IDD_PREFERENCE_SUB_SEARCHING + 3) #define IDC_CHECK_CONFIRMREPLOPENDOCS (IDD_PREFERENCE_SUB_SEARCHING + 4) #define IDC_CHECK_REPLACEANDSTOP (IDD_PREFERENCE_SUB_SEARCHING + 5) + #define IDC_CHECK_SHOWONCEPERFOUNDLINE (IDD_PREFERENCE_SUB_SEARCHING + 6) #define IDD_PREFERENCE_SUB_DARKMODE 7100 //(IDD_PREFERENCE_BOX + 1100) #define IDC_CHECK_DARKMODE_ENABLE (IDD_PREFERENCE_SUB_DARKMODE + 1) diff --git a/lexilla/lexers/LexSearchResult.cxx b/lexilla/lexers/LexSearchResult.cxx index c3630e9ec..5b956f346 100644 --- a/lexilla/lexers/LexSearchResult.cxx +++ b/lexilla/lexers/LexSearchResult.cxx @@ -79,18 +79,23 @@ static void ColouriseSearchResultLine(SearchResultMarkings* pMarkings, char *lin int currentStat = SCE_SEARCHRESULT_DEFAULT; - SearchResultMarking mi = pMarkings->_markings[linenum]; + SearchResultMarkingLine miLine = pMarkings->_markings[linenum]; - size_t match_start = startLine + mi._start - 1; - size_t match_end = startLine + mi._end - 1; + for (std::pair mi : miLine._segmentPostions) + { + size_t match_start = startLine + mi.first - 1; + size_t match_end = startLine + mi.second - 1; - if (match_start <= endPos) { - styler.ColourTo(match_start, SCE_SEARCHRESULT_DEFAULT); - if (match_end <= endPos) - styler.ColourTo(match_end, SCE_SEARCHRESULT_WORD2SEARCH); - else - currentStat = SCE_SEARCHRESULT_WORD2SEARCH; + if (match_start <= endPos) + { + styler.ColourTo(match_start, SCE_SEARCHRESULT_DEFAULT); + if (match_end <= endPos) + styler.ColourTo(match_end, SCE_SEARCHRESULT_WORD2SEARCH); + else + currentStat = SCE_SEARCHRESULT_WORD2SEARCH; + } } + styler.ColourTo(endPos, currentStat); } else // every character - search header @@ -117,19 +122,24 @@ static void ColouriseSearchResultDoc(Sci_PositionU startPos, Sci_Position length if (!pMarkings || !pMarkings->_markings) return; - for (size_t i = startPos; i < startPos + length; i++) { + for (size_t i = startPos; i < startPos + length; i++) + { lineBuffer[linePos++] = styler[i]; - if (AtEOL(styler, i) || (linePos >= sizeof(lineBuffer) - 1)) { + if (AtEOL(styler, i) || (linePos >= sizeof(lineBuffer) - 1)) + { // End of line (or of line buffer) met, colourise it lineBuffer[linePos] = '\0'; ColouriseSearchResultLine(pMarkings, lineBuffer, startLine, i, styler, styler.GetLine(startLine)); linePos = 0; startLine = i + 1; - while (!AtEOL(styler, i)) i++; + + while (!AtEOL(styler, i)) + i++; } } - if (linePos > 0) { // Last line does not have ending characters + if (linePos > 0) // Last line does not have ending characters + { ColouriseSearchResultLine(pMarkings, lineBuffer, startLine, startPos + length - 1, styler, styler.GetLine(startLine)); } } diff --git a/scintilla/include/Scintilla.h b/scintilla/include/Scintilla.h index 8733e3959..36facc0aa 100644 --- a/scintilla/include/Scintilla.h +++ b/scintilla/include/Scintilla.h @@ -1359,14 +1359,14 @@ struct SCNotification { int characterSource; /* SCN_CHARADDED */ }; -struct SearchResultMarking { - intptr_t _start; - intptr_t _end; +#include +struct SearchResultMarkingLine { // each line could have several segments if user want to see only 1 found line which contains several results + std::vector> _segmentPostions; // a vector of pair of start & end of occurrence for colourizing }; struct SearchResultMarkings { intptr_t _length; - SearchResultMarking *_markings; + SearchResultMarkingLine *_markings; }; #ifdef INCLUDE_DEPRECATED_FEATURES