diff --git a/PowerEditor/src/MISC/Common/Common.cpp b/PowerEditor/src/MISC/Common/Common.cpp index 9578cf72..96d97eb2 100644 --- a/PowerEditor/src/MISC/Common/Common.cpp +++ b/PowerEditor/src/MISC/Common/Common.cpp @@ -718,7 +718,7 @@ generic_string stringReplace(generic_string subject, const generic_string& searc return subject; } -std::vector stringSplit(const generic_string& input, generic_string delimiter) +std::vector stringSplit(const generic_string& input, const generic_string& delimiter) { auto start = 0U; auto end = input.find(delimiter); @@ -734,7 +734,7 @@ std::vector stringSplit(const generic_string& input, generic_str return output; } -generic_string stringJoin(const std::vector &strings, generic_string separator) +generic_string stringJoin(const std::vector& strings, const generic_string& separator) { generic_string joined; size_t length = strings.size(); @@ -747,4 +747,133 @@ generic_string stringJoin(const std::vector &strings, generic_st } } return joined; +} + +long long stollStrict(const generic_string& input) +{ + if (input.empty()) + { + throw std::invalid_argument("Empty input."); + } + else + { + // Check minus characters. + const int minuses = std::count(input.begin(), input.end(), TEXT('-')); + if (minuses > 1) + { + throw std::invalid_argument("More than one minus sign."); + } + else if (minuses == 1 && input[0] != TEXT('-')) + { + throw std::invalid_argument("Minus sign must be first."); + } + + // Check for other characters which are not allowed. + if (input.find_first_not_of(TEXT("-0123456789")) != std::string::npos) + { + throw std::invalid_argument("Invalid character found."); + } + + return std::stoll(input); + } +} + +bool allLinesAreNumericOrEmpty(const std::vector& lines) +{ + for (const generic_string& line : lines) + { + try + { + if (!line.empty()) + { + stollStrict(line); + } + } + catch (std::invalid_argument&) + { + return false; + } + catch (std::out_of_range&) + { + return false; + } + } + return true; +} + +std::vector repeatString(const generic_string& text, const size_t count) +{ + std::vector output; + output.reserve(count); + for (size_t i = 0; i < count; ++i) + { + output.push_back(text); + } + assert(output.size() == count); + return output; +} + +std::vector lexicographicSort(std::vector input, bool isDescending) +{ + std::sort(input.begin(), input.end(), [isDescending](generic_string a, generic_string b) + { + if (isDescending) + { + return a.compare(b) > 0; + } + else + { + return a.compare(b) < 0; + } + }); + return input; +} + +std::vector numericSort(std::vector input, bool isDescending) +{ + // Pre-condition: all strings in "input" are either empty or convertible to int with stoiStrict. + // Note that empty lines are filtered out and added back manually to the output at the end. + std::vector nonEmptyInputAsNumbers; + size_t nofEmptyLines = 0; + nonEmptyInputAsNumbers.reserve(input.size()); + for (const generic_string& line : input) + { + if (line.empty()) + { + ++nofEmptyLines; + } + else + { + nonEmptyInputAsNumbers.push_back(stollStrict(line)); + } + } + assert(nonEmptyinputAsInts.size() + nofEmptyLines == input.size()); + std::sort(nonEmptyInputAsNumbers.begin(), nonEmptyInputAsNumbers.end(), [isDescending](long long a, long long b) + { + if (isDescending) + { + return a > b; + } + else + { + return a < b; + } + }); + std::vector output; + output.reserve(input.size()); + const std::vector empties = repeatString(TEXT(""), nofEmptyLines); + if (!isDescending) + { + output.insert(output.end(), empties.begin(), empties.end()); + } + for (const long long& sortedNumber : nonEmptyInputAsNumbers) + { + output.push_back(std::to_wstring(sortedNumber)); + } + if (isDescending) + { + output.insert(output.end(), empties.begin(), empties.end()); + } + assert(output.size() == input.size()); + return output; } \ No newline at end of file diff --git a/PowerEditor/src/MISC/Common/Common.h b/PowerEditor/src/MISC/Common/Common.h index 43bc0e6a..2e90fea6 100644 --- a/PowerEditor/src/MISC/Common/Common.h +++ b/PowerEditor/src/MISC/Common/Common.h @@ -188,7 +188,13 @@ generic_string PathAppend(generic_string &strDest, const generic_string & str2ap COLORREF getCtrlBgColor(HWND hWnd); generic_string stringToUpper(generic_string strToConvert); generic_string stringReplace(generic_string subject, const generic_string& search, const generic_string& replace); -std::vector stringSplit(const generic_string& input, generic_string delimiter); -generic_string stringJoin(const std::vector &strings, generic_string separator); +std::vector stringSplit(const generic_string& input, const generic_string& delimiter); +generic_string stringJoin(const std::vector& strings, const generic_string& separator); +long long stollStrict(const generic_string& input); +bool allLinesAreNumericOrEmpty(const std::vector& lines); +std::vector repeatString(const generic_string& text, const size_t count); + +std::vector numericSort(std::vector input, bool isDescending); +std::vector lexicographicSort(std::vector input, bool isDescending); #endif //M30_IDE_COMMUN_H diff --git a/PowerEditor/src/NppCommands.cpp b/PowerEditor/src/NppCommands.cpp index f852e7fd..0dbd252c 100644 --- a/PowerEditor/src/NppCommands.cpp +++ b/PowerEditor/src/NppCommands.cpp @@ -375,7 +375,7 @@ void Notepad_plus::command(int id) } _pEditView->execute(SCI_BEGINUNDOACTION); - _pEditView->quickSortLines(fromLine, toLine, id == IDM_EDIT_SORTLINES_DESCENDING); + _pEditView->sortLines(fromLine, toLine, id == IDM_EDIT_SORTLINES_DESCENDING); _pEditView->execute(SCI_ENDUNDOACTION); if (hasSelection) // there was 1 selection, so we restore it diff --git a/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp b/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp index ed0f9cc4..91cad99a 100644 --- a/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp +++ b/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp @@ -1703,7 +1703,7 @@ generic_string ScintillaEditView::getGenericTextAsString(int start, int end) con { assert(end > start); const int bufSize = end - start + 1; - _TCHAR *buf = new _TCHAR[bufSize]; + TCHAR *buf = new TCHAR[bufSize]; getGenericText(buf, bufSize, start, end); generic_string text = buf; delete[] buf; @@ -2947,7 +2947,7 @@ void ScintillaEditView::insertNewLineBelowCurrentLine() execute(SCI_SETEMPTYSELECTION, execute(SCI_POSITIONFROMLINE, current_line + 1)); } -void ScintillaEditView::quickSortLines(size_t fromLine, size_t toLine, bool isDescending) +void ScintillaEditView::sortLines(size_t fromLine, size_t toLine, bool isDescending) { if (fromLine >= toLine) { @@ -2958,19 +2958,35 @@ void ScintillaEditView::quickSortLines(size_t fromLine, size_t toLine, bool isDe const int endPos = execute(SCI_POSITIONFROMLINE, toLine) + execute(SCI_LINELENGTH, toLine); const generic_string text = getGenericTextAsString(startPos, endPos); std::vector splitText = stringSplit(text, getEOLString()); - std::sort(splitText.begin(), splitText.end(), [isDescending](generic_string a, generic_string b) + const size_t lineCount = execute(SCI_GETLINECOUNT); + const bool sortEntireDocument = toLine == lineCount - 1; + if (!sortEntireDocument) { - if (isDescending) + if (splitText.rbegin()->empty()) { - return a.compare(b) > 0; + splitText.pop_back(); } - else - { - return a.compare(b) < 0; - } - }); - const generic_string joined = stringJoin(splitText, getEOLString()); - replaceTarget(joined.c_str(), startPos, endPos); + } + assert(toLine - fromLine + 1 == splitText.size()); + const bool isNumericSort = allLinesAreNumericOrEmpty(splitText); + std::vector sortedText; + if (isNumericSort) + { + sortedText = numericSort(splitText, isDescending); + } + else + { + sortedText = lexicographicSort(splitText, isDescending); + } + const generic_string joined = stringJoin(sortedText, getEOLString()); + if (sortEntireDocument) + { + replaceTarget(joined.c_str(), startPos, endPos); + } + else + { + replaceTarget((joined + getEOLString()).c_str(), startPos, endPos); + } } bool ScintillaEditView::isTextDirectionRTL() const diff --git a/PowerEditor/src/ScitillaComponent/ScintillaEditView.h b/PowerEditor/src/ScitillaComponent/ScintillaEditView.h index e296ba64..0dcf90ae 100644 --- a/PowerEditor/src/ScitillaComponent/ScintillaEditView.h +++ b/PowerEditor/src/ScitillaComponent/ScintillaEditView.h @@ -636,7 +636,7 @@ public: }; void scrollPosToCenter(int pos); generic_string getEOLString(); - void quickSortLines(size_t fromLine, size_t toLine, bool isDescending); + void sortLines(size_t fromLine, size_t toLine, bool isDescending); void changeTextDirection(bool isRTL); bool isTextDirectionRTL() const;