diff --git a/CHANGELOG.md b/CHANGELOG.md index ff61f7f..0638614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Columns++ for Notepad++ -- Releases +## Version 1.0.1 -- December 14th, 2023 + +* Added Width as an option for custom sorts; addresses Issue #15 feature request. + ## Version 1.0 -- November 17th, 2023 * This will be the first version submitted for inclusion in Plugins Admin. diff --git a/help.htm b/help.htm index c80e4ec..26d6070 100644 --- a/help.htm +++ b/help.htm @@ -79,7 +79,7 @@ p.subsub {margin-left: 1.5em;} -table.optionsTable {border: none; margin: 1em 0 1em 1em; border-collapse: collapse;} +table.optionsTable {border: none; margin: 1em 0 1em 1em; width: calc(100% - 1em - 2px); border-collapse: collapse;} table.optionsTable th {padding: .5em .5em .5em .5em; font-weight: bold; text-align: left; vertical-align: top; border: 1px solid black;} table.optionsTable td {padding: .5em .5em .5em .5em; font-weight: normal; text-align: left; vertical-align: top; border: 1px solid black;} table.optionsTable .group {text-align: center; background: #d0d0d0; } @@ -661,13 +661,18 @@

Custom sorts

Note: This will result in blank-padding lines in the selection which do not extend to or past the right boundary of the column selection. If elastic tabstops are enabled and the number of tabs included in the column selection is different on different lines (for example, because some lines are short), results using Selected text only are unlikely to be as expected.

+ + + + +
Sort direction
AscendingSmaller numbers, narrower text, or characters earlier in the collating sequence, come first.
DescendingLarger numbers, wider text, or characters later in the collating sequence, come first.
+ - - +
Sort type
AscendingSmaller numbers, or characters earlier in the collating sequence, come first.
DescendingLarger numbers, or characters later in the collating sequence, come first.
BinaryThe raw byte values of the internal representations of the selected sort strings are used as sort keys. For most purposes, this matches what you would expect from a “case sensitive” sort, with the sort order dependent on the active code page. Unicode files sort by code point.
LocaleThe sort order is defined by a Windows locale, as specified in the Locale sort details section.
NumericSort strings are interpreted as numbers, as described in the Number formats section. Strings which can’t be interpreted as numbers sort first (whether the sort is ascending or descending). When Regular expression is selected, the regular expression is used to parse the selected text on each line; in all other cases, the text is interpreted as a sequence of tab-separated values.
WidthThe visible width of the selected sort strings are used as keys.
@@ -680,7 +685,7 @@

Custom sorts

+

Each sort key number may be followed (without intervening spaces) by one of the letters a or d, and/or one of the letters b, l, n or w. These specify ascending, descending, binary, locale, numeric and width, overriding the selections in the Sort direction and/or Sort type boxes for the capture group or tab field to which they are appended.

Match caseWhen checked, the regular expression match is case sensitive; otherwise, the case of the text is ignored.
Specify keys using capture groupsWhen checked, the Keys box specifies the sort sequence in terms of capture groups. When unchecked, the text matched by the regular expression is used as the sort key.
Keys

A list of keys, separated by spaces, commas and/or semicolons, to be used for sorting. The major sort key is listed first, with subsequent keys having lower precedence. Each key is designated with a number. If Tabbed is selected, the number indicates a tab-separated field, numbered left to right counting from 1; 0 represents the entire selected text in the line. If Regular expression is selected, the number is the number of a capture group; 0 represents the entire match.

-

Each sort key number may be followed (without intervening spaces) by one of the letters a or d, and/or one of the letters b, l or n. These specify ascending, descending, binary, locale and numeric, overriding the selection in the Sort type box for the capture group or tab field to which they are appended.

diff --git a/src/ColumnsPlusPlus.cpp b/src/ColumnsPlusPlus.cpp index bf89e3a..b8a46bd 100644 --- a/src/ColumnsPlusPlus.cpp +++ b/src/ColumnsPlusPlus.cpp @@ -116,33 +116,6 @@ INT_PTR CALLBACK elasticProgressDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wPara } -int unwrappedWidth(ColumnsPlusPlusData& data, Scintilla::Position from, Scintilla::Position to) { - auto& sci = data.sci; - int width = 0; - int xLoc = sci.PointXFromPosition(from); - int xEnd = sci.PointXFromPosition(to); - if (sci.WrapMode() != Scintilla::Wrap::None && sci.WrapCount(sci.LineFromPosition(from)) > 1) { - int yLoc = sci.PointYFromPosition(from); - int yEnd = sci.PointYFromPosition(to); - for (Scintilla::Position next = from + 1; yLoc != yEnd; ++next) { - int yNext = sci.PointYFromPosition(next); - while (yNext == yLoc) { - ++next; - yNext = sci.PointYFromPosition(next); - } - width += sci.PointXFromPosition(next - 1) - xLoc; - char lastBeforeWrap = sci.CharacterAt(next - 1); - char cStringBeforeWrap[] = " "; - if (lastBeforeWrap) cStringBeforeWrap[0] = lastBeforeWrap; - width += sci.TextWidth(sci.StyleIndexAt(next - 1), cStringBeforeWrap); - xLoc = sci.PointXFromPosition(next); - yLoc = yNext; - } - } - return xEnd - xLoc; -} - - void ColumnsPlusPlusData::setTabstops(DocumentData& dd, Scintilla::Line firstNeeded, Scintilla::Line lastNeeded) { ElasticProgressInfo epi(*this); epi.ddp = ⅆ @@ -245,7 +218,7 @@ bool ElasticProgressInfo::setTabstops(bool stepless) { size_t tabIndex = leadingTabCount; size_t from = 0; for (TabLayoutBlock* tlb = &dd.tabLayouts[tlbIndex]; tabIndex < tabOffsets.size(); ++tabIndex) { - int width = unwrappedWidth(data, lineStarts + from, lineStarts + tabOffsets[tabIndex]) + tabGap; + int width = data.unwrappedWidth(lineStarts + from, lineStarts + tabOffsets[tabIndex]) + tabGap; if (width > tlb->width) tlb->width = width; size_t i = 0; if (i < tlb->right.size() && tlb->right[i].lastLine < lineNum) ++i; @@ -321,7 +294,7 @@ bool ElasticProgressInfo::analyzeTabstops() { TabLayoutBlock& tlb = layouts->back(); tlb.lastLine = lineNum; int width = dd.assumeMonospace ? static_cast(sci.CountCharacters(begin + from, begin + tab)) * digitWidth - : unwrappedWidth(data, begin + from, begin + tab); + : data.unwrappedWidth(begin + from, begin + tab); width += tabGap + indentSize; indentSize = 0; if (width > tlb.width) tlb.width = width; diff --git a/src/ColumnsPlusPlus.h b/src/ColumnsPlusPlus.h index e2e6951..aeda47d 100644 --- a/src/ColumnsPlusPlus.h +++ b/src/ColumnsPlusPlus.h @@ -249,7 +249,7 @@ class SortSettings { std::vector regexHistory; std::vector keygroupHistory; std::wstring localeName; - enum SortType {Binary, Locale, Numeric } sortType = Binary; + enum SortType {Binary, Locale, Numeric, Width } sortType = Binary; enum KeyType {EntireColumn, IgnoreBlanks, Tabbed, Regex} keyType = EntireColumn; bool sortColumnSelectionOnly = false; bool sortDescending = false; @@ -479,6 +479,31 @@ class ColumnsPlusPlusData { } } + int unwrappedWidth(Scintilla::Position from, Scintilla::Position to) { + int width = 0; + int xLoc = sci.PointXFromPosition(from); + int xEnd = sci.PointXFromPosition(to); + if (sci.WrapMode() != Scintilla::Wrap::None && sci.WrapCount(sci.LineFromPosition(from)) > 1) { + int yLoc = sci.PointYFromPosition(from); + int yEnd = sci.PointYFromPosition(to); + for (Scintilla::Position next = from + 1; yLoc != yEnd; ++next) { + int yNext = sci.PointYFromPosition(next); + while (yNext == yLoc) { + ++next; + yNext = sci.PointYFromPosition(next); + } + width += sci.PointXFromPosition(next - 1) - xLoc; + char lastBeforeWrap = sci.CharacterAt(next - 1); + char cStringBeforeWrap[] = " "; + if (lastBeforeWrap) cStringBeforeWrap[0] = lastBeforeWrap; + width += sci.TextWidth(sci.StyleIndexAt(next - 1), cStringBeforeWrap); + xLoc = sci.PointXFromPosition(next); + yLoc = yNext; + } + } + return xEnd - xLoc; + } + // ColumnsPlusPlus.cpp void analyzeTabstops(DocumentData& dd); diff --git a/src/ColumnsPlusPlus.rc b/src/ColumnsPlusPlus.rc index 772ade9..eaf572b 100644 Binary files a/src/ColumnsPlusPlus.rc and b/src/ColumnsPlusPlus.rc differ diff --git a/src/Configuration.cpp b/src/Configuration.cpp index a3bcf4d..c4b23f4 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -246,6 +246,7 @@ void ColumnsPlusPlusData::loadConfiguration() { strlwr(value.data()); sort.sortType = value == "locale" ? SortSettings::Locale : value == "numeric" ? SortSettings::Numeric + : value == "width" ? SortSettings::Width : SortSettings::Binary; } else if (setting == "keytype") { @@ -429,6 +430,7 @@ void ColumnsPlusPlusData::saveConfiguration() { file << "localeIgnoreSymbols\t" << sort.localeIgnoreSymbols << std::endl; file << "sortType\t" << ( sort.sortType == SortSettings::Locale ? "Locale" : sort.sortType == SortSettings::Numeric ? "Numeric" + : sort.sortType == SortSettings::Width ? "Width" : "Binary" ) << std::endl; file << "keyType\t" << ( sort.keyType == SortSettings::IgnoreBlanks ? "IgnoreBlanks" : sort.keyType == SortSettings::Tabbed ? "Tabbed" diff --git a/src/Sort.cpp b/src/Sort.cpp index 022a604..993d089 100644 --- a/src/Sort.cpp +++ b/src/Sort.cpp @@ -23,12 +23,12 @@ namespace { -const std::wregex keycap(L"(?:(\\d+)(?:([ad][bln]?|[bln][ad]?)?))" +const std::wregex keycap(L"(?:(\\d+)(?:([ad][blnw]?|[blnw][ad]?)?))" L"(?:" L"[,; ]*" L"(" - L"\\d+(?:(?:[ad][bln]?|[bln][ad]?)?)" - L"(?:[,; ]*\\d+(?:(?:[ad][bln]?|[bln][ad]?)?))*" + L"\\d+(?:(?:[ad][blnw]?|[blnw][ad]?)?)" + L"(?:[,; ]*\\d+(?:(?:[ad][blnw]?|[blnw][ad]?)?))*" L")" L")?" , std::wregex::icase | std::wregex::optimize); @@ -182,6 +182,7 @@ void sortCommon(ColumnsPlusPlusData& data, const SortSettings& sortSettings, Rec if (t.find_first_of(L"bB") != std::wstring::npos) capType.push_back(SortSettings::Binary ); else if (t.find_first_of(L"lL") != std::wstring::npos) capType.push_back(SortSettings::Locale ); else if (t.find_first_of(L"nN") != std::wstring::npos) capType.push_back(SortSettings::Numeric); + else if (t.find_first_of(L"wW") != std::wstring::npos) capType.push_back(SortSettings::Width ); else capType.push_back(sortSettings.sortType); s = m[3]; } @@ -234,6 +235,13 @@ void sortCommon(ColumnsPlusPlusData& data, const SortSettings& sortSettings, Rec for (size_t i = 0; i < capGroup.size(); ++i) { std::string s = rx.str(capGroup[i]); if (capType[i] == SortSettings::Numeric) ss[n].keys.emplace_back(data.parseNumber(s), capDesc[i]); + else if (capType[i] == SortSettings::Width) { + if (s.empty()) ss[n].keys.emplace_back(0, capDesc[i]); + else { + Scintilla::Position start = row.cpMin() + rx.position(capGroup[i]); + ss[n].keys.emplace_back(data.unwrappedWidth(start, start + s.length()), capDesc[i]); + } + } else { if (capType[i] == SortSettings::Locale) s = getLocaleSortKey(s, codepage, options, locale); ss[n].keys.emplace_back(s, capDesc[i]); @@ -242,12 +250,24 @@ void sortCommon(ColumnsPlusPlusData& data, const SortSettings& sortSettings, Rec } } else if (sortSettings.keyType == SortSettings::Tabbed) { - std::vector cells; - cells.emplace_back(row.text()); - for (const auto& cell : row) cells.push_back(cell.text()); + std::vector cellText; + std::vector cellStart; + std::vector cellEnd; + cellText .emplace_back(row.text()); + cellStart.emplace_back(row.cpMin()); + cellEnd .emplace_back(row.cpMax()); + for (const auto& cell : row) { + cellText .push_back(cell.text()); + cellStart.push_back(cell.start()); + cellEnd .push_back(cell.end()); + } for (size_t i = 0; i < capGroup.size(); ++i) { - std::string s = capGroup[i] < cells.size() ? cells[capGroup[i]] : ""; + std::string s = capGroup[i] < cellText.size() ? cellText[capGroup[i]] : ""; if (capType[i] == SortSettings::Numeric) ss[n].keys.emplace_back(data.parseNumber(s), capDesc[i]); + else if (capType[i] == SortSettings::Width) { + if (capGroup[i] >= cellStart.size()) ss[n].keys.emplace_back(0, capDesc[i]); + else ss[n].keys.emplace_back(data.unwrappedWidth(cellStart[capGroup[i]], cellEnd[capGroup[i]]), capDesc[i]); + } else { if (capType[i] == SortSettings::Locale) s = getLocaleSortKey(s, codepage, options, locale); ss[n].keys.emplace_back(s, capDesc[i]); @@ -256,6 +276,20 @@ void sortCommon(ColumnsPlusPlusData& data, const SortSettings& sortSettings, Rec } else if (sortSettings.sortType == SortSettings::Numeric) for (const auto& cell : row) ss[n].keys.emplace_back(data.parseNumber(cell.trim()), sortSettings.sortDescending); + else if (sortSettings.sortType == SortSettings::Width) { + Scintilla::Position start = row.cpMin(); + Scintilla::Position end = row.cpMax(); + if (sortSettings.keyType == SortSettings::IgnoreBlanks) { + std::string s(row.text()); + size_t i = s.find_first_not_of("\t "); + if (i == std::string::npos) end = start; + else { + end = start + s.find_last_not_of("\t ") + 1; + start += i; + } + } + ss[n].keys.emplace_back(data.unwrappedWidth(start, end), sortSettings.sortDescending); + } else { std::string s(row.text()); if (sortSettings.keyType == SortSettings::IgnoreBlanks) { @@ -341,9 +375,10 @@ INT_PTR CALLBACK sortDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM l if (data.sort.sortDescending) CheckRadioButton(hwndDlg, IDC_SORT_ASCENDING, IDC_SORT_DESCENDING, IDC_SORT_DESCENDING); else CheckRadioButton(hwndDlg, IDC_SORT_ASCENDING, IDC_SORT_DESCENDING, IDC_SORT_ASCENDING ); switch (data.sort.sortType) { - case SortSettings::Binary : CheckRadioButton(hwndDlg, IDC_SORT_BINARY, IDC_SORT_NUMERIC, IDC_SORT_BINARY ); break; - case SortSettings::Locale : CheckRadioButton(hwndDlg, IDC_SORT_BINARY, IDC_SORT_NUMERIC, IDC_SORT_LOCALE ); break; - case SortSettings::Numeric: CheckRadioButton(hwndDlg, IDC_SORT_BINARY, IDC_SORT_NUMERIC, IDC_SORT_NUMERIC); break; + case SortSettings::Binary : CheckRadioButton(hwndDlg, IDC_SORT_BINARY, IDC_SORT_WIDTH, IDC_SORT_BINARY ); break; + case SortSettings::Locale : CheckRadioButton(hwndDlg, IDC_SORT_BINARY, IDC_SORT_WIDTH, IDC_SORT_LOCALE ); break; + case SortSettings::Numeric: CheckRadioButton(hwndDlg, IDC_SORT_BINARY, IDC_SORT_WIDTH, IDC_SORT_NUMERIC); break; + case SortSettings::Width : CheckRadioButton(hwndDlg, IDC_SORT_BINARY, IDC_SORT_WIDTH, IDC_SORT_WIDTH ); break; } switch (data.sort.keyType) { case SortSettings::EntireColumn: CheckRadioButton(hwndDlg, IDC_SORT_ENTIRE_COLUMN, IDC_SORT_REGEX, IDC_SORT_ENTIRE_COLUMN); break; @@ -463,9 +498,10 @@ INT_PTR CALLBACK sortDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM l } else data.sort.keyType = IsDlgButtonChecked(hwndDlg, IDC_SORT_IGNORE_BLANKS) == BST_CHECKED ? SortSettings::IgnoreBlanks : SortSettings::EntireColumn; - data.sort.sortType = IsDlgButtonChecked(hwndDlg, IDC_SORT_BINARY) == BST_CHECKED ? SortSettings::Binary - : IsDlgButtonChecked(hwndDlg, IDC_SORT_LOCALE) == BST_CHECKED ? SortSettings::Locale - : SortSettings::Numeric; + data.sort.sortType = IsDlgButtonChecked(hwndDlg, IDC_SORT_LOCALE ) == BST_CHECKED ? SortSettings::Locale + : IsDlgButtonChecked(hwndDlg, IDC_SORT_NUMERIC) == BST_CHECKED ? SortSettings::Numeric + : IsDlgButtonChecked(hwndDlg, IDC_SORT_WIDTH ) == BST_CHECKED ? SortSettings::Width + : SortSettings::Binary; data.sort.sortColumnSelectionOnly = IsDlgButtonChecked(hwndDlg, IDC_SORT_WITHIN_SELECTION ) == BST_CHECKED; data.sort.sortDescending = IsDlgButtonChecked(hwndDlg, IDC_SORT_DESCENDING ) == BST_CHECKED; data.sort.localeCaseSensitive = IsDlgButtonChecked(hwndDlg, IDC_SORT_CASE_SENSITIVE ) == BST_CHECKED; diff --git a/src/resource.h b/src/resource.h index 72213ca..94d243d 100644 --- a/src/resource.h +++ b/src/resource.h @@ -163,39 +163,40 @@ #define IDC_SORT_BINARY 1157 #define IDC_SORT_LOCALE 1158 #define IDC_SORT_NUMERIC 1159 -#define IDC_SORT_FIND_WHAT 1160 -#define IDC_SORT_MATCH_CASE 1161 -#define IDC_SORT_USE_KEY 1162 -#define IDC_SORT_KEY_CAPTURE 1163 -#define IDC_SORT_LOCALE_LANGUAGE 1164 -#define IDC_SORT_LOCALE_NAME 1165 -#define IDC_SORT_LOCALE_SORT 1166 -#define IDC_SORT_CASE_SENSITIVE 1167 -#define IDC_SORT_DIGITS_AS_NUMBERS 1168 -#define IDC_SORT_IGNORE_DIACRITICS 1169 -#define IDC_SORT_IGNORE_SYMBOLS 1170 -#define IDC_SORT_FIND_WHAT_LABEL 1171 -#define IDC_SORT_KEY_CAPTURE_LABEL 1172 -#define IDC_OPTIONS_INDICATOR_ALPHA_LABEL 1173 -#define IDC_OPTIONS_INDICATOR_RED_LABEL 1174 -#define IDC_OPTIONS_INDICATOR_GREEN_LABEL 1175 -#define IDC_OPTIONS_INDICATOR_BLUE_LABEL 1176 -#define IDC_OPTIONS_INDICATOR_NUMBER_LABEL 1177 -#define IDC_TIME_FORMATS_DAYS 1178 -#define IDC_TIME_FORMATS_HOURS 1179 -#define IDC_TIME_FORMATS_MINUTES 1180 -#define IDC_TIME_FORMATS_SECONDS 1181 -#define IDC_TIME_FORMATS_PARTIAL_0 1182 -#define IDC_TIME_FORMATS_PARTIAL_1 1183 -#define IDC_TIME_FORMATS_PARTIAL_2 1184 -#define IDC_TIME_FORMATS_PARTIAL_3 1185 -#define IDC_TIME_FORMATS_ENABLE_0 1186 -#define IDC_TIME_FORMATS_ENABLE_1 1187 -#define IDC_TIME_FORMATS_ENABLE_2 1188 -#define IDC_TIME_FORMATS_ENABLE_3 1189 -#define IDC_TIME_FORMATS_LABEL_0 1190 -#define IDC_TIME_FORMATS_LABEL_1 1191 -#define IDC_TIME_FORMATS_LABEL_2 1192 +#define IDC_SORT_WIDTH 1160 +#define IDC_SORT_FIND_WHAT 1161 +#define IDC_SORT_MATCH_CASE 1162 +#define IDC_SORT_USE_KEY 1163 +#define IDC_SORT_KEY_CAPTURE 1164 +#define IDC_SORT_LOCALE_LANGUAGE 1165 +#define IDC_SORT_LOCALE_NAME 1166 +#define IDC_SORT_LOCALE_SORT 1167 +#define IDC_SORT_CASE_SENSITIVE 1168 +#define IDC_SORT_DIGITS_AS_NUMBERS 1169 +#define IDC_SORT_IGNORE_DIACRITICS 1170 +#define IDC_SORT_IGNORE_SYMBOLS 1171 +#define IDC_SORT_FIND_WHAT_LABEL 1172 +#define IDC_SORT_KEY_CAPTURE_LABEL 1173 +#define IDC_OPTIONS_INDICATOR_ALPHA_LABEL 1174 +#define IDC_OPTIONS_INDICATOR_RED_LABEL 1175 +#define IDC_OPTIONS_INDICATOR_GREEN_LABEL 1176 +#define IDC_OPTIONS_INDICATOR_BLUE_LABEL 1177 +#define IDC_OPTIONS_INDICATOR_NUMBER_LABEL 1178 +#define IDC_TIME_FORMATS_DAYS 1179 +#define IDC_TIME_FORMATS_HOURS 1180 +#define IDC_TIME_FORMATS_MINUTES 1181 +#define IDC_TIME_FORMATS_SECONDS 1182 +#define IDC_TIME_FORMATS_PARTIAL_0 1183 +#define IDC_TIME_FORMATS_PARTIAL_1 1184 +#define IDC_TIME_FORMATS_PARTIAL_2 1185 +#define IDC_TIME_FORMATS_PARTIAL_3 1186 +#define IDC_TIME_FORMATS_ENABLE_0 1187 +#define IDC_TIME_FORMATS_ENABLE_1 1188 +#define IDC_TIME_FORMATS_ENABLE_2 1189 +#define IDC_TIME_FORMATS_ENABLE_3 1190 +#define IDC_TIME_FORMATS_LABEL_0 1191 +#define IDC_TIME_FORMATS_LABEL_1 1192 +#define IDC_TIME_FORMATS_LABEL_2 1193 #define IDC_CALCULATION_TIME_AUTO 1194 #define IDC_CALCULATION_TIME1 1200 #define IDC_CALCULATION_TIME2 1201 @@ -230,7 +231,7 @@ #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 141 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1238 +#define _APS_NEXT_CONTROL_VALUE 1239 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif