Skip to content

Commit 677d2de

Browse files
committed
fixed Encoding error for Search line and hit marking; right padding line number
1 parent 0b82b8d commit 677d2de

File tree

4 files changed

+96
-46
lines changed

4 files changed

+96
-46
lines changed

src/Encoding.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ std::string Encoding::bytesToUtf8(const std::string_view src, UINT codePage)
4242
return u8;
4343
}
4444

45+
//
46+
// multibyte (raw bytes, given code page) → std::wstring
47+
//
48+
std::wstring Encoding::bytesToWString(const std::string& raw, UINT cp)
49+
{
50+
if (cp == CP_UTF8)
51+
return Encoding::utf8ToWString(raw);
52+
// ANSI/andere Codepage
53+
int wlen = MultiByteToWideChar(cp, 0, raw.data(), (int)raw.size(), nullptr, 0);
54+
std::wstring wstr(wlen, 0);
55+
MultiByteToWideChar(cp, 0, raw.data(), (int)raw.size(), wstr.data(), wlen);
56+
return wstr;
57+
}
58+
4559

4660
//
4761
// UTF‑8 → std::wstring

src/Encoding.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Encoding final
99
public:
1010
// raw bytes(document code page) → UTF‑8 std::string
1111
static std::string bytesToUtf8(const std::string_view src, UINT codePage);
12+
static std::wstring Encoding::bytesToWString(const std::string& raw, UINT cp);
1213

1314
// UTF‑8 ⇄ std::wstring
1415
static std::wstring utf8ToWString(const std::string& utf8);

src/MultiReplacePanel.cpp

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6027,13 +6027,18 @@ void MultiReplace::CloseDebugWindow() {
60276027
#pragma region Find All
60286028

60296029
std::wstring MultiReplace::sanitizeSearchPattern(const std::wstring& raw) {
6030-
// Only escape CR and LF sequences, leave other characters unchanged
6031-
std::string utf8 = Encoding::wstringToUtf8(raw);
6032-
std::string escaped = replaceNewline(utf8, MultiReplace::ReplaceMode::Regex);
6033-
return Encoding::stringToWString(escaped);
6030+
// Escape newlines directly in the wstring (no encoding conversion needed)
6031+
std::wstring escaped = raw;
6032+
// Replace CR/LF as before, but for wstring
6033+
size_t pos = 0;
6034+
while ((pos = escaped.find(L"\r", pos)) != std::wstring::npos)
6035+
escaped.replace(pos, 1, L"\\r"), pos += 2;
6036+
pos = 0;
6037+
while ((pos = escaped.find(L"\n", pos)) != std::wstring::npos)
6038+
escaped.replace(pos, 1, L"\\n"), pos += 2;
6039+
return escaped;
60346040
}
60356041

6036-
60376042
void MultiReplace::trimHitToFirstLine(
60386043
const std::function<LRESULT(UINT, WPARAM, LPARAM)>& sciSend,
60396044
ResultDock::Hit& h)
@@ -6252,6 +6257,7 @@ void MultiReplace::handleFindAllButton()
62526257

62536258
showStatusMessage(getLangStr(L"status_occurrences_found", { std::to_wstring(totalHits) }), MessageStatus::Success);
62546259
}
6260+
62556261
#pragma endregion
62566262

62576263

@@ -9026,22 +9032,17 @@ void MultiReplace::showListFilePath()
90269032
}
90279033

90289034
std::wstring MultiReplace::getSelectedText() {
9029-
SIZE_T length = SendMessage(nppData._scintillaMainHandle, SCI_GETSELTEXT, 0, 0);
9030-
9031-
if (length > MAX_TEXT_LENGTH) {
9032-
return L"";
9033-
}
9034-
9035-
char* buffer = new char[length + 1]; // Add 1 for null terminator
9036-
SendMessage(nppData._scintillaMainHandle, SCI_GETSELTEXT, 0, (LPARAM)buffer);
9037-
buffer[length] = '\0';
9035+
Sci_Position length = static_cast<Sci_Position>(SendMessage(nppData._scintillaMainHandle, SCI_GETSELTEXT, 0, 0));
9036+
if (length <= 0 || length > MAX_TEXT_LENGTH) return L"";
90389037

9039-
std::string str(buffer);
9040-
std::wstring wstr = Encoding::stringToWString(str);
9038+
std::string buffer(length, '\0');
9039+
SendMessage(nppData._scintillaMainHandle, SCI_GETSELTEXT, 0, (LPARAM)&buffer[0]);
9040+
buffer.resize(strnlen(buffer.c_str(), length));
90419041

9042-
delete[] buffer;
9042+
UINT sciCp = static_cast<UINT>(SendMessage(nppData._scintillaMainHandle, SCI_GETCODEPAGE, 0, 0));
9043+
UINT cp = (sciCp == SC_CP_UTF8 ? CP_UTF8 : (sciCp ? sciCp : CP_ACP));
90439044

9044-
return wstr;
9045+
return Encoding::bytesToWString(buffer, cp);
90459046
}
90469047

90479048
LRESULT MultiReplace::getEOLLengthForLine(LRESULT line) {

src/ResultDock.cpp

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -334,73 +334,107 @@ void ResultDock::formatHitsLines(const SciSendFn& sciSend,
334334
constexpr wchar_t INDENT_W[] = L" ";
335335
constexpr size_t INDENT_U8 = 12;
336336

337+
// Get the document code page (ANSI, UTF-8, etc.)
337338
const UINT docCp = static_cast<UINT>(sciSend(SCI_GETCODEPAGE, 0, 0));
338339

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+
339359
int prevLine = -1;
340360
Hit* firstHitOnLine = nullptr;
341361

362+
// STEP 2: Iterate hits and build output lines
363+
size_t hitIdx = 0;
342364
for (Hit& h : hits)
343365
{
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;
346368

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));
349371
std::string raw(rawLen, '\0');
350372
sciSend(SCI_GETLINE, lineZero, (LPARAM)raw.data());
351373
raw.resize(strnlen(raw.c_str(), rawLen));
374+
// Trim any CR/LF at end
352375
while (!raw.empty() && (raw.back() == '\r' || raw.back() == '\n'))
353376
raw.pop_back();
354377

355-
// --- trim leading blanks -------------------------------
378+
// Trim leading spaces/tabs
356379
size_t lead = raw.find_first_not_of(" \t");
357380
std::string_view sliceBytes =
358-
(lead == std::string::npos) ? std::string_view{}
381+
(lead == std::string::npos)
382+
? std::string_view{}
359383
: std::string_view(raw).substr(lead);
360384

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
363389
const std::wstring sliceW = Encoding::utf8ToWString(sliceU8);
364390

365-
// --- prefix --------------------------------------------
391+
// Build the line‑number prefix (with right alignment)
392+
std::wstring paddedNum = padLineNumber(lineOne, maxDigits);
366393
std::wstring prefixW = std::wstring(INDENT_W)
367-
+ L"Line " + std::to_wstring(lineOne) + L": ";
394+
+ L"Line " + paddedNum + L": ";
368395
size_t prefixU8Len = Encoding::wstringToUtf8(prefixW).size();
369396

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);
373401

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();
377405
size_t hitLenU8 = Encoding::bytesToUtf8(
378-
sliceBytes.substr(relBytes, h.length), docCp).size();
406+
std::string(sliceBytes.substr(relBytes, h.length)), docCp).size();
379407

380-
// --- first hit on this visual line? --------------------
408+
// If this is the first hit on a new line, output the full line
381409
if (lineZero != prevLine)
382410
{
383411
out += prefixW + sliceW + L"\r\n";
384412

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) };
390420

421+
// Advance utf8Pos by the number of bytes inserted (prefix + content + CRLF)
391422
utf8Pos += prefixU8Len + sliceU8.size() + 2;
423+
392424
firstHitOnLine = &h;
393425
prevLine = lineZero;
394426
}
395-
else // additional hit on same line
427+
else
396428
{
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
401434
}
402435
}
403436

437+
// Remove dummy entries (all but the first hit per visual line)
404438
hits.erase(std::remove_if(hits.begin(), hits.end(),
405439
[](const Hit& h) { return h.displayLineStart < 0; }),
406440
hits.end());

0 commit comments

Comments
 (0)