Skip to content

Commit 0bd711b

Browse files
committed
identify Level by spaces
1 parent a925252 commit 0bd711b

File tree

3 files changed

+160
-122
lines changed

3 files changed

+160
-122
lines changed

src/ResultDock.cpp

Lines changed: 138 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,97 @@
4141

4242
extern NppData nppData;
4343

44+
// ==========================================================================
45+
// ResultDock – Menu actions (copy / open lines & paths) – C++17
46+
// ==========================================================================
47+
48+
namespace {
49+
50+
// --------------------------------------------------------------------------
51+
// Line‑classification helper
52+
// --------------------------------------------------------------------------
53+
enum class LineKind { Blank, SearchHdr, FileHdr, CritHdr, HitLine };
54+
55+
LineKind classify(const std::string& raw) {
56+
size_t spaces = 0;
57+
while (spaces < raw.size() && raw[spaces] == ' ') ++spaces;
58+
59+
std::string_view trimmed(raw.data() + spaces, raw.size() - spaces);
60+
if (trimmed.empty()) return LineKind::Blank;
61+
62+
// Hier vollqualifizieren:
63+
for (int lvl = 0; lvl <= static_cast<int>(ResultDock::LineLevel::HitLine); ++lvl) {
64+
if ((int)spaces == ResultDock::INDENT_SPACES[lvl]) {
65+
switch (static_cast<ResultDock::LineLevel>(lvl)) {
66+
case ResultDock::LineLevel::SearchHdr: return LineKind::SearchHdr;
67+
case ResultDock::LineLevel::FileHdr: return LineKind::FileHdr;
68+
case ResultDock::LineLevel::CritHdr: return LineKind::CritHdr;
69+
case ResultDock::LineLevel::HitLine: return LineKind::HitLine;
70+
}
71+
}
72+
}
73+
return LineKind::Blank;
74+
}
75+
76+
// --------------------------------------------------------------------------
77+
// Remove "Line 123: " prefix from hit line (UTF‑16)
78+
// --------------------------------------------------------------------------
79+
std::wstring stripHitPrefix(const std::wstring& w) {
80+
size_t i = 0;
81+
82+
while (i < w.size() && iswspace(w[i])) ++i; // leading WS
83+
while (i < w.size() && iswalpha(w[i])) ++i; // "Line", "Zeile", …
84+
if (i < w.size() && w[i] == L' ') ++i; // single space
85+
86+
size_t digitStart = i;
87+
while (i < w.size() && iswdigit(w[i])) ++i; // digits
88+
if (i == digitStart || i >= w.size()) return w; // no digits → bail
89+
90+
if (w[i] != L':') return w; // must be ':'
91+
++i;
92+
93+
while (i < w.size() && iswspace(w[i])) ++i; // WS after ':'
94+
return w.substr(i); // remainder = hit
95+
}
96+
97+
// --------------------------------------------------------------------------
98+
// Extract absolute path from a file‑header line
99+
// --------------------------------------------------------------------------
100+
std::wstring pathFromFileHdr(const std::wstring& w) {
101+
std::wstring s = w;
102+
if (s.rfind(L" ", 0) == 0) s.erase(0, 4); // remove indent
103+
size_t p = s.rfind(L" (");
104+
if (p != std::wstring::npos) s.erase(p); // drop " (n hits)"
105+
return s;
106+
}
107+
108+
// --------------------------------------------------------------------------
109+
// Find the nearest ancestor file‑header line
110+
// --------------------------------------------------------------------------
111+
int ancestorFileLine(HWND hSci, int startLine) {
112+
for (int l = startLine; l >= 0; --l) {
113+
int len = (int)::SendMessage(hSci, SCI_LINELENGTH, l, 0);
114+
std::string raw(len, '\0');
115+
::SendMessage(hSci, SCI_GETLINE, l, (LPARAM)raw.data());
116+
raw.resize(strnlen(raw.c_str(), len));
117+
if (classify(raw) == LineKind::FileHdr) return l;
118+
}
119+
return -1;
120+
}
121+
122+
// --------------------------------------------------------------------------
123+
// Read one physical Scintilla line as UTF‑16
124+
// --------------------------------------------------------------------------
125+
std::wstring getLineW(HWND hSci, int line) {
126+
int len = (int)::SendMessage(hSci, SCI_LINELENGTH, line, 0);
127+
std::string raw(len, '\0');
128+
::SendMessage(hSci, SCI_GETLINE, line, (LPARAM)raw.data());
129+
raw.resize(strnlen(raw.c_str(), len));
130+
return Encoding::utf8ToWString(raw);
131+
}
132+
133+
} // anonymous namespace
134+
44135
ResultDock::ResultDock(HINSTANCE hInst)
45136
: _hInst(hInst) // member‑initialiser‑list
46137
, _hSci(nullptr)
@@ -322,59 +413,66 @@ void ResultDock::rebuildFolding()
322413
for (int l = 0; l < lineCount; ++l)
323414
sciSend(SCI_SETFOLDLEVEL, l, BASE);
324415

325-
// Pass 2: detect headers via leading spaces & keywords
326-
auto isSearchHeader = [](const std::string& s) -> bool
327-
{
328-
return s.rfind("Search ", 0) == 0; // starts with "Search "
329-
};
330-
416+
// Pass 2: detect semantic levels purely by leading spaces via classify()
331417
for (int l = 0; l < lineCount; ++l)
332418
{
333-
const int rawLen = static_cast<int>(sciSend(SCI_LINELENGTH, l, 0));
334-
if (rawLen <= 1) continue; // blank line
419+
const int rawLen = static_cast<int>(sciSend(SCI_LINELENGTH, l));
420+
if (rawLen <= 1) continue; // skip blank or extremely short lines
335421

336-
std::string buf(rawLen + 1, '\0');
422+
// Read the raw line text (without CR/LF)
423+
std::string buf(rawLen, '\0');
337424
sciSend(SCI_GETLINE, l, (LPARAM)buf.data());
338-
buf.erase(std::find_if(buf.begin(), buf.end(), [](char c)
339-
{ return c == '\r' || c == '\n' || c == '\0'; }), buf.end());
425+
buf.resize(strnlen(buf.c_str(), rawLen));
340426

341427
// Count leading spaces
342428
int spaces = 0;
343429
while (spaces < (int)buf.size() && buf[spaces] == ' ') ++spaces;
344430

345-
// Remove indent for easier tests
346-
const std::string_view trimmed(buf.data() + spaces, buf.size() - spaces);
347-
348-
bool header = false;
431+
// Classify into one of our semantic levels
432+
auto kind = classify(buf);
433+
bool isHeader = false;
349434
int level = BASE;
350435

351-
if (spaces == 0 && (trimmed.rfind("Search in List", 0) == 0 || isSearchHeader(std::string(trimmed))))
352-
{
353-
header = true;
354-
level = BASE; // 0
355-
}
356-
else if (spaces == 4)
357-
{
358-
if (isSearchHeader(std::string(trimmed))) { header = true; level = BASE + 2; } // Old list entry (rare)
359-
else { header = true; level = BASE + 1; } // File path
360-
}
361-
else if (spaces == 8 && isSearchHeader(std::string(trimmed)))
436+
switch (kind)
362437
{
363-
header = true;
364-
level = BASE + 2; // Criteria
365-
}
366-
else {
367-
// Content line → level = BASE + 3 (or deeper) handled later
438+
case LineKind::SearchHdr:
439+
// top‑level search header at indent 0
440+
isHeader = true;
441+
level = BASE + static_cast<int>(ResultDock::LineLevel::SearchHdr);
442+
break;
443+
444+
case LineKind::FileHdr:
445+
// file header at indent 4
446+
isHeader = true;
447+
level = BASE + static_cast<int>(ResultDock::LineLevel::FileHdr);
448+
break;
449+
450+
case LineKind::CritHdr:
451+
// criterion header at indent 8 (grouped view only)
452+
isHeader = true;
453+
level = BASE + static_cast<int>(ResultDock::LineLevel::CritHdr);
454+
break;
455+
456+
case LineKind::HitLine:
457+
// hits are folded as content, see below
458+
break;
459+
460+
default:
461+
// Blank or unknown indent → treat as content
462+
break;
368463
}
369464

370-
if (header)
465+
if (isHeader)
371466
{
467+
// set fold header for SearchHdr, FileHdr, CritHdr
372468
sciSend(SCI_SETFOLDLEVEL, l, level | SC_FOLDLEVELHEADERFLAG);
373469
}
374470
else
375471
{
376-
int contentLevel = BASE + (spaces / 4);
377-
if (contentLevel < BASE) contentLevel = BASE;
472+
// all other lines (including hits) get a content level
473+
// depth = spaces / indent for FileHdr (4 spaces)
474+
int depth = spaces / INDENT_SPACES[static_cast<int>(ResultDock::LineLevel::FileHdr)];
475+
int contentLevel = BASE + (std::max)(depth, 1);
378476
sciSend(SCI_SETFOLDLEVEL, l, contentLevel);
379477
}
380478
}
@@ -390,8 +488,10 @@ void ResultDock::formatHitsForFile(const std::wstring& wPath,
390488
std::wstring& out,
391489
size_t& utf8Pos) const
392490
{
393-
std::wstring hdr = L" " + wPath +
394-
L" (" + std::to_wstring(hits.size()) + L" hits)\r\n";
491+
// compute indent for FileHdr from matrix
492+
std::wstring indentFile = std::wstring(ResultDock::INDENT_SPACES[static_cast<int>(ResultDock::LineLevel::FileHdr)], L' ');
493+
494+
std::wstring hdr = indentFile + wPath + L" (" + std::to_wstring(hits.size()) + L" hits)\r\n";
395495
out += hdr;
396496
utf8Pos += Encoding::wstringToUtf8(hdr).size();
397497

@@ -1010,7 +1110,7 @@ LRESULT CALLBACK ResultDock::sciSubclassProc(HWND hwnd, UINT msg, WPARAM wp, LPA
10101110
raw.resize(strnlen(raw.c_str(), lineLen));
10111111

10121112
// 4) Bail if this line does *not* contain "Line "
1013-
if (raw.find(marker) == std::string::npos)
1113+
if (classify(raw) != LineKind::HitLine)
10141114
return 0;
10151115

10161116
// 5) Count *all* previous "Line " occurrences to get our hitIndex
@@ -1020,7 +1120,7 @@ LRESULT CALLBACK ResultDock::sciSubclassProc(HWND hwnd, UINT msg, WPARAM wp, LPA
10201120
std::string buf(len, '\0');
10211121
::SendMessage(hwnd, SCI_GETLINE, i, (LPARAM)&buf[0]);
10221122
buf.resize(strnlen(buf.c_str(), len));
1023-
if (buf.find(marker) != std::string::npos)
1123+
if (classify(buf) == LineKind::HitLine)
10241124
++hitIndex;
10251125
}
10261126

@@ -1213,89 +1313,6 @@ LRESULT CALLBACK ResultDock::sciSubclassProc(HWND hwnd, UINT msg, WPARAM wp, LPA
12131313
: ::DefWindowProc(hwnd, msg, wp, lp);
12141314
}
12151315

1216-
// ==========================================================================
1217-
// ResultDock – Menu actions (copy / open lines & paths) – C++17
1218-
// ==========================================================================
1219-
1220-
namespace {
1221-
1222-
// --------------------------------------------------------------------------
1223-
// Line‑classification helper
1224-
// --------------------------------------------------------------------------
1225-
enum class LineKind { Blank, SearchHdr, FileHdr, CritHdr, HitLine };
1226-
1227-
LineKind classify(const std::string& raw) {
1228-
size_t spaces = 0;
1229-
while (spaces < raw.size() && raw[spaces] == ' ') ++spaces;
1230-
1231-
std::string_view trim(raw.data() + spaces, raw.size() - spaces);
1232-
if (trim.empty()) return LineKind::Blank;
1233-
if (spaces == 0) return LineKind::SearchHdr;
1234-
if (spaces == 4 && trim.rfind("Search ", 0) != 0) return LineKind::FileHdr;
1235-
if (spaces == 8 && trim.rfind("Search ", 0) == 0) return LineKind::CritHdr;
1236-
if (spaces >= 12 && trim.rfind("Line ", 0) == 0) return LineKind::HitLine;
1237-
return LineKind::Blank;
1238-
}
1239-
1240-
// --------------------------------------------------------------------------
1241-
// Remove "Line 123: " prefix from hit line (UTF‑16)
1242-
// --------------------------------------------------------------------------
1243-
std::wstring stripHitPrefix(const std::wstring& w) {
1244-
size_t i = 0;
1245-
1246-
while (i < w.size() && iswspace(w[i])) ++i; // leading WS
1247-
while (i < w.size() && iswalpha(w[i])) ++i; // "Line", "Zeile", …
1248-
if (i < w.size() && w[i] == L' ') ++i; // single space
1249-
1250-
size_t digitStart = i;
1251-
while (i < w.size() && iswdigit(w[i])) ++i; // digits
1252-
if (i == digitStart || i >= w.size()) return w; // no digits → bail
1253-
1254-
if (w[i] != L':') return w; // must be ':'
1255-
++i;
1256-
1257-
while (i < w.size() && iswspace(w[i])) ++i; // WS after ':'
1258-
return w.substr(i); // remainder = hit
1259-
}
1260-
1261-
// --------------------------------------------------------------------------
1262-
// Extract absolute path from a file‑header line
1263-
// --------------------------------------------------------------------------
1264-
std::wstring pathFromFileHdr(const std::wstring& w) {
1265-
std::wstring s = w;
1266-
if (s.rfind(L" ", 0) == 0) s.erase(0, 4); // remove indent
1267-
size_t p = s.rfind(L" (");
1268-
if (p != std::wstring::npos) s.erase(p); // drop " (n hits)"
1269-
return s;
1270-
}
1271-
1272-
// --------------------------------------------------------------------------
1273-
// Find the nearest ancestor file‑header line
1274-
// --------------------------------------------------------------------------
1275-
int ancestorFileLine(HWND hSci, int startLine) {
1276-
for (int l = startLine; l >= 0; --l) {
1277-
int len = (int)::SendMessage(hSci, SCI_LINELENGTH, l, 0);
1278-
std::string raw(len, '\0');
1279-
::SendMessage(hSci, SCI_GETLINE, l, (LPARAM)raw.data());
1280-
raw.resize(strnlen(raw.c_str(), len));
1281-
if (classify(raw) == LineKind::FileHdr) return l;
1282-
}
1283-
return -1;
1284-
}
1285-
1286-
// --------------------------------------------------------------------------
1287-
// Read one physical Scintilla line as UTF‑16
1288-
// --------------------------------------------------------------------------
1289-
std::wstring getLineW(HWND hSci, int line) {
1290-
int len = (int)::SendMessage(hSci, SCI_LINELENGTH, line, 0);
1291-
std::string raw(len, '\0');
1292-
::SendMessage(hSci, SCI_GETLINE, line, (LPARAM)raw.data());
1293-
raw.resize(strnlen(raw.c_str(), len));
1294-
return Encoding::utf8ToWString(raw);
1295-
}
1296-
1297-
} // anonymous namespace
1298-
12991316

13001317
// ==========================================================================
13011318
// copySelectedLines (IDM_RD_COPY_LINES)

src/ResultDock.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,22 @@ class ResultDock final
101101
static void setWrapEnabled(bool v) { _wrapEnabled = v; }
102102
static void setPurgeEnabled(bool v) { _purgeOnNextSearch = v; }
103103

104+
/// Semantic levels in the result dock
105+
enum class LineLevel : int {
106+
SearchHdr = 0, // top‑level search header
107+
FileHdr = 1, // file header (only in grouped view)
108+
CritHdr = 2, // criterion header (only in grouped view)
109+
HitLine = 3 // actual hit line
110+
};
111+
112+
/// Number of spaces to indent for each LineLevel
113+
static constexpr int INDENT_SPACES[] = {
114+
/* SearchHdr */ 0,
115+
/* FileHdr */ 4,
116+
/* CritHdr */ 8,
117+
/* HitLine */ 12
118+
};
119+
104120
private:
105121
// --------------------- Theme Colors ------------------------
106122
// Holds all relevant colors for the dock panel (light/dark)

src/language_mapping.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ std::unordered_map<std::wstring, std::wstring> languageMap = {
227227
{ L"rdmenu_clear_all", L"Clear all" },
228228
{ L"rdmenu_open_paths", L"Open selected pathname(s)" },
229229
{ L"rdmenu_wrap", L"Word wrap long lines" },
230-
{ L"rdmenu_purge", L"Purge for every search" }
230+
{ L"rdmenu_purge", L"Purge for every search" },
231+
232+
{ L"dock_list_header", L"Search in List ($REPLACE_STRING1 hits)" },
233+
{ L"dock_single_header", L"Search \"$REPLACE_STRING1\" ($REPLACE_STRING2 hits)" },
234+
{ L"dock_line", L"line" },
235+
231236

232237
};

0 commit comments

Comments
 (0)