41
41
42
42
extern NppData nppData;
43
43
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
+
44
135
ResultDock::ResultDock (HINSTANCE hInst)
45
136
: _hInst(hInst) // member‑initialiser‑list
46
137
, _hSci(nullptr )
@@ -322,59 +413,66 @@ void ResultDock::rebuildFolding()
322
413
for (int l = 0 ; l < lineCount; ++l)
323
414
sciSend (SCI_SETFOLDLEVEL, l, BASE);
324
415
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()
331
417
for (int l = 0 ; l < lineCount; ++l)
332
418
{
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
335
421
336
- std::string buf (rawLen + 1 , ' \0 ' );
422
+ // Read the raw line text (without CR/LF)
423
+ std::string buf (rawLen, ' \0 ' );
337
424
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));
340
426
341
427
// Count leading spaces
342
428
int spaces = 0 ;
343
429
while (spaces < (int )buf.size () && buf[spaces] == ' ' ) ++spaces;
344
430
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 ;
349
434
int level = BASE;
350
435
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)
362
437
{
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 ;
368
463
}
369
464
370
- if (header )
465
+ if (isHeader )
371
466
{
467
+ // set fold header for SearchHdr, FileHdr, CritHdr
372
468
sciSend (SCI_SETFOLDLEVEL, l, level | SC_FOLDLEVELHEADERFLAG);
373
469
}
374
470
else
375
471
{
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 );
378
476
sciSend (SCI_SETFOLDLEVEL, l, contentLevel);
379
477
}
380
478
}
@@ -390,8 +488,10 @@ void ResultDock::formatHitsForFile(const std::wstring& wPath,
390
488
std::wstring& out,
391
489
size_t & utf8Pos) const
392
490
{
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 " ;
395
495
out += hdr;
396
496
utf8Pos += Encoding::wstringToUtf8 (hdr).size ();
397
497
@@ -1010,7 +1110,7 @@ LRESULT CALLBACK ResultDock::sciSubclassProc(HWND hwnd, UINT msg, WPARAM wp, LPA
1010
1110
raw.resize (strnlen (raw.c_str (), lineLen));
1011
1111
1012
1112
// 4) Bail if this line does *not* contain "Line "
1013
- if (raw. find (marker) == std::string::npos )
1113
+ if (classify (raw) != LineKind::HitLine )
1014
1114
return 0 ;
1015
1115
1016
1116
// 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
1020
1120
std::string buf (len, ' \0 ' );
1021
1121
::SendMessage (hwnd, SCI_GETLINE, i, (LPARAM)&buf[0]);
1022
1122
buf.resize (strnlen (buf.c_str (), len));
1023
- if (buf. find (marker) != std::string::npos )
1123
+ if (classify (buf) == LineKind::HitLine )
1024
1124
++hitIndex;
1025
1125
}
1026
1126
@@ -1213,89 +1313,6 @@ LRESULT CALLBACK ResultDock::sciSubclassProc(HWND hwnd, UINT msg, WPARAM wp, LPA
1213
1313
: ::DefWindowProc (hwnd, msg, wp, lp);
1214
1314
}
1215
1315
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
-
1299
1316
1300
1317
// ==========================================================================
1301
1318
// copySelectedLines (IDM_RD_COPY_LINES)
0 commit comments