@@ -334,73 +334,107 @@ void ResultDock::formatHitsLines(const SciSendFn& sciSend,
334
334
constexpr wchar_t INDENT_W[] = L" " ;
335
335
constexpr size_t INDENT_U8 = 12 ;
336
336
337
+ // Get the document code page (ANSI, UTF-8, etc.)
337
338
const UINT docCp = static_cast <UINT>(sciSend (SCI_GETCODEPAGE, 0 , 0 ));
338
339
340
+ // STEP 1: Determine line numbers and max digit width for right alignment
341
+ std::vector<int > lineNumbers;
342
+ lineNumbers.reserve (hits.size ());
343
+ size_t maxDigits = 0 ;
344
+ for (const auto & h : hits) {
345
+ int lineZero = static_cast <int >(sciSend (SCI_LINEFROMPOSITION, h.pos , 0 ));
346
+ int lineOne = lineZero + 1 ;
347
+ lineNumbers.push_back (lineOne);
348
+ size_t digits = std::to_wstring (lineOne).size ();
349
+ maxDigits = (std::max)(maxDigits, digits);
350
+ }
351
+
352
+ // Helper: pad a line number to given width (right‑aligned)
353
+ auto padLineNumber = [](int number, size_t width) -> std::wstring {
354
+ std::wstring numStr = std::to_wstring (number);
355
+ size_t pad = (width > numStr.size () ? width - numStr.size () : 0 );
356
+ return std::wstring (pad, L' ' ) + numStr;
357
+ };
358
+
339
359
int prevLine = -1 ;
340
360
Hit* firstHitOnLine = nullptr ;
341
361
362
+ // STEP 2: Iterate hits and build output lines
363
+ size_t hitIdx = 0 ;
342
364
for (Hit& h : hits)
343
365
{
344
- int lineZero = ( int ) sciSend (SCI_LINEFROMPOSITION, h. pos , 0 ) ;
345
- int lineOne = lineZero + 1 ;
366
+ int lineOne = lineNumbers[hitIdx++] ;
367
+ int lineZero = lineOne - 1 ;
346
368
347
- // --- read raw bytes ------------------------------------
348
- int rawLen = ( int ) sciSend (SCI_LINELENGTH, lineZero, 0 );
369
+ // Read raw bytes of this line from Scintilla
370
+ int rawLen = static_cast < int >( sciSend (SCI_LINELENGTH, lineZero, 0 ) );
349
371
std::string raw (rawLen, ' \0 ' );
350
372
sciSend (SCI_GETLINE, lineZero, (LPARAM)raw.data ());
351
373
raw.resize (strnlen (raw.c_str (), rawLen));
374
+ // Trim any CR/LF at end
352
375
while (!raw.empty () && (raw.back () == ' \r ' || raw.back () == ' \n ' ))
353
376
raw.pop_back ();
354
377
355
- // --- trim leading blanks -------------------------------
378
+ // Trim leading spaces/tabs
356
379
size_t lead = raw.find_first_not_of (" \t " );
357
380
std::string_view sliceBytes =
358
- (lead == std::string::npos) ? std::string_view{}
381
+ (lead == std::string::npos)
382
+ ? std::string_view{}
359
383
: std::string_view (raw).substr (lead);
360
384
361
- // --- FULL slice as UTF‑8 (★) ---------------------------
362
- const std::string sliceU8 = Encoding::bytesToUtf8 (sliceBytes, docCp);
385
+ // --- IMPORTANT: produce two representations ---
386
+ // 1) UTF-8 bytes for accurate byte‑offset highlighting
387
+ const std::string sliceU8 = Encoding::bytesToUtf8 (std::string (sliceBytes), docCp);
388
+ // 2) Wide‑string for display in the dock
363
389
const std::wstring sliceW = Encoding::utf8ToWString (sliceU8);
364
390
365
- // --- prefix --------------------------------------------
391
+ // Build the line‑number prefix (with right alignment)
392
+ std::wstring paddedNum = padLineNumber (lineOne, maxDigits);
366
393
std::wstring prefixW = std::wstring (INDENT_W)
367
- + L" Line " + std::to_wstring (lineOne) + L" : " ;
394
+ + L" Line " + paddedNum + L" : " ;
368
395
size_t prefixU8Len = Encoding::wstringToUtf8 (prefixW).size ();
369
396
370
- // --- byte offset of hit inside slice -------------------
371
- Sci_Position absTrimmed = sciSend (SCI_POSITIONFROMLINE, lineZero, 0 ) + (Sci_Position)lead;
372
- size_t relBytes = (size_t )(h.pos - absTrimmed);
397
+ // Compute byte‑offset of match start within sliceU8
398
+ Sci_Position absTrimmed = sciSend (SCI_POSITIONFROMLINE, lineZero, 0 )
399
+ + static_cast <Sci_Position>(lead);
400
+ size_t relBytes = static_cast <size_t >(h.pos - absTrimmed);
373
401
374
- // --- locate hit in UTF‑8 text (★) ---------------------
375
- size_t hitStartInSlice = Encoding::bytesToUtf8 (
376
- sliceBytes.substr (0 , relBytes), docCp).size ();
402
+ // Compute byte‑lengths for the match portion
403
+ size_t hitStartInSliceU8 = Encoding::bytesToUtf8 (
404
+ std::string ( sliceBytes.substr (0 , relBytes) ), docCp).size ();
377
405
size_t hitLenU8 = Encoding::bytesToUtf8 (
378
- sliceBytes.substr (relBytes, h.length ), docCp).size ();
406
+ std::string ( sliceBytes.substr (relBytes, h.length ) ), docCp).size ();
379
407
380
- // --- first hit on this visual line? --------------------
408
+ // If this is the first hit on a new line, output the full line
381
409
if (lineZero != prevLine)
382
410
{
383
411
out += prefixW + sliceW + L" \r\n " ;
384
412
385
- h.displayLineStart = (int )utf8Pos;
386
- h.numberStart = (int )(INDENT_U8 + 5 );
387
- h.numberLen = (int )std::to_string (lineOne).size ();
388
- h.matchStarts = { (int )(prefixU8Len + hitStartInSlice) };
389
- h.matchLens = { (int )hitLenU8 };
413
+ h.displayLineStart = static_cast <int >(utf8Pos);
414
+ h.numberStart = static_cast <int >(INDENT_U8 + 5 + paddedNum.size () - std::to_wstring (lineOne).size ());
415
+ h.numberLen = static_cast <int >(std::to_wstring (lineOne).size ());
416
+
417
+ // ** Use byte‑based offsets for highlighting **
418
+ h.matchStarts = { static_cast <int >(prefixU8Len + hitStartInSliceU8) };
419
+ h.matchLens = { static_cast <int >(hitLenU8) };
390
420
421
+ // Advance utf8Pos by the number of bytes inserted (prefix + content + CRLF)
391
422
utf8Pos += prefixU8Len + sliceU8.size () + 2 ;
423
+
392
424
firstHitOnLine = &h;
393
425
prevLine = lineZero;
394
426
}
395
- else // additional hit on same line
427
+ else
396
428
{
397
- assert (firstHitOnLine != nullptr );
398
- firstHitOnLine->matchStarts .push_back ((int )(prefixU8Len + hitStartInSlice));
399
- firstHitOnLine->matchLens .push_back ((int )hitLenU8);
400
- h.displayLineStart = -1 ; // dummy
429
+ // Additional hit on the same visual line: append to firstHitOnLine
430
+ assert (firstHitOnLine);
431
+ firstHitOnLine->matchStarts .push_back (static_cast <int >(prefixU8Len + hitStartInSliceU8));
432
+ firstHitOnLine->matchLens .push_back (static_cast <int >(hitLenU8));
433
+ h.displayLineStart = -1 ; // mark as dummy so it's removed later
401
434
}
402
435
}
403
436
437
+ // Remove dummy entries (all but the first hit per visual line)
404
438
hits.erase (std::remove_if (hits.begin (), hits.end (),
405
439
[](const Hit& h) { return h.displayLineStart < 0 ; }),
406
440
hits.end ());
0 commit comments