Skip to content

Commit

Permalink
Add Width as a sort option. (Addresses Issue #15 feature request.)
Browse files Browse the repository at this point in the history
  • Loading branch information
Coises committed Dec 14, 2023
1 parent 17cb3b8 commit 4464946
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 81 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
13 changes: 9 additions & 4 deletions help.htm
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -661,13 +661,18 @@ <h3>Custom sorts</h3>
<p><em>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 <strong>Selected text only</strong> are unlikely to be as expected.</em></p></td></tr>
</table>

<table class=optionsTable>
<tr><th colspan=2 class=group>Sort direction</th></tr>
<tr><th>Ascending</th><td>Smaller numbers, narrower text, or characters earlier in the collating sequence, come first.</td></tr>
<tr><th>Descending</th><td>Larger numbers, wider text, or characters later in the collating sequence, come first.</td></tr>
</table>

<table class=optionsTable>
<tr><th colspan=2 class=group>Sort type</th></tr>
<tr><th>Ascending</th><td>Smaller numbers, or characters earlier in the collating sequence, come first.</td></tr>
<tr><th>Descending</th><td>Larger numbers, or characters later in the collating sequence, come first.</td></tr>
<tr><th>Binary</th><td>The 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.</td></tr>
<tr><th>Locale</th><td>The sort order is defined by a Windows locale, as specified in the <strong>Locale sort details</strong> section.</td></tr>
<tr><th>Numeric</th><td>Sort strings are interpreted as numbers, as described in the <a href="#numberformats">Number formats</a> section. Strings which can’t be interpreted as numbers sort first (whether the sort is ascending or descending). When <strong>Regular expression</strong> 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.</td></tr>
<tr><th>Width</th><td>The visible width of the selected sort strings are used as keys.</td></tr>
</table>

<table class=optionsTable>
Expand All @@ -680,7 +685,7 @@ <h3>Custom sorts</h3>
<tr><th>Match case</th><td>When checked, the regular expression match is case sensitive; otherwise, the case of the text is ignored.</td></tr>
<tr><th>Specify keys using capture groups</th><td>When checked, the <strong>Keys</strong> 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.</td></tr>
<tr><th>Keys</th><td><p>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 <strong>Tabbed</strong> 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 <strong>Regular expression</strong> is selected, the number is the number of a capture group; 0 represents the entire match.</p>
<p>Each sort key number may be followed (without intervening spaces) by one of the letters <strong>a</strong> or <strong>d</strong>, and/or one of the letters <strong>b</strong>, <strong>l</strong> or <strong>n</strong>. These specify <strong>a</strong>scending, <strong>d</strong>escending, <strong>b</strong>inary, <strong>l</strong>ocale and <strong>n</strong>umeric, overriding the selection in the <strong>Sort type</strong> box for the capture group or tab field to which they are appended.</p></td></tr>
<p>Each sort key number may be followed (without intervening spaces) by one of the letters <strong>a</strong> or <strong>d</strong>, and/or one of the letters <strong>b</strong>, <strong>l</strong>, <strong>n</strong> or <strong>w</strong>. These specify <strong>a</strong>scending, <strong>d</strong>escending, <strong>b</strong>inary, <strong>l</strong>ocale, <strong>n</strong>umeric and <strong>w</strong>idth, overriding the selections in the <strong>Sort direction</strong> and/or <strong>Sort type</strong> boxes for the capture group or tab field to which they are appended.</p></td></tr>
</table>

<table class=optionsTable>
Expand Down
31 changes: 2 additions & 29 deletions src/ColumnsPlusPlus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 = &dd;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -321,7 +294,7 @@ bool ElasticProgressInfo::analyzeTabstops() {
TabLayoutBlock& tlb = layouts->back();
tlb.lastLine = lineNum;
int width = dd.assumeMonospace ? static_cast<int>(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;
Expand Down
27 changes: 26 additions & 1 deletion src/ColumnsPlusPlus.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ class SortSettings {
std::vector<std::wstring> regexHistory;
std::vector<std::wstring> 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;
Expand Down Expand Up @@ -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);
Expand Down
Binary file modified src/ColumnsPlusPlus.rc
Binary file not shown.
2 changes: 2 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -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"
Expand Down
62 changes: 49 additions & 13 deletions src/Sort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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];
}
Expand Down Expand Up @@ -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]);
Expand All @@ -242,12 +250,24 @@ void sortCommon(ColumnsPlusPlusData& data, const SortSettings& sortSettings, Rec
}
}
else if (sortSettings.keyType == SortSettings::Tabbed) {
std::vector<std::string> cells;
cells.emplace_back(row.text());
for (const auto& cell : row) cells.push_back(cell.text());
std::vector<std::string > cellText;
std::vector<Scintilla::Position> cellStart;
std::vector<Scintilla::Position> 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]);
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 4464946

Please sign in to comment.