refactor(contacts): share case-insensitive compare, truncation helpers
The contacts list carried its own lowercase-and-compare sort, its own lowercase substring search, and an inline linear middle-truncation loop for fitting an address to a column width. These duplicated logic that belongs in the shared text helpers. - A new allocation-free case-insensitive compare replaces the per-sort lowercasing; it lowercases ASCII per byte and leaves bytes >= 0x80 alone, so UTF-8 labels sort exactly as before. - Search now uses the shared case-insensitive contains helper. - Middle-truncation to a pixel width moves to a shared template that binary-searches the fit (about log2(n) width measurements instead of up to n) while producing the same result as the old linear shrink. Tests: the compare matches lowercase-then-compare across ASCII, UTF-8 bytes and length ties, and the fit helper matches the old linear loop across lengths and widths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include "util/text_format.h"
|
||||
#include "chat/chat_crypto.h"
|
||||
#include "chat/chat_identity.h"
|
||||
#include "chat/chat_service.h"
|
||||
@@ -18062,6 +18063,41 @@ void testFillStrip() {
|
||||
EXPECT_NEAR(tri, trap, 1e-3f);
|
||||
}
|
||||
|
||||
void testContactsTextHelpers() {
|
||||
// compareIgnoreCase == lower+compare, including UTF-8 bytes and length ties
|
||||
auto ref = [](std::string a, std::string b) {
|
||||
for (auto& c : a) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
for (auto& c : b) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
int r = a.compare(b); return r < 0 ? -1 : r > 0 ? 1 : 0;
|
||||
};
|
||||
const std::vector<std::string> v = {"", "a", "A", "ab", "AB", "Zed", "zed", "\xC3\x89t\xC3\xA9", "\xC3\xA9", "z", "Z1", "z10"};
|
||||
for (auto& x : v) for (auto& y : v) {
|
||||
int r = dragonx::util::compareIgnoreCase(x, y); r = r < 0 ? -1 : r > 0 ? 1 : 0;
|
||||
EXPECT_EQ(r, ref(x, y));
|
||||
}
|
||||
// truncateMiddleToFit == the old linear loop for every width
|
||||
auto meas = [](const std::string& t) { float w = 0; for (unsigned char c : t) w += 5 + (c % 7); return w; };
|
||||
auto oldFit = [&](const std::string& s, int mf, int mb, float maxW) {
|
||||
const std::string floor = dragonx::util::truncateMiddle(s, mf, mb);
|
||||
if (meas(s) <= maxW) return s;
|
||||
if (maxW <= 0.0f) return floor;
|
||||
const std::string ell = "\xE2\x80\xA6";
|
||||
size_t head = s.size() / 2, tail = s.size() - head;
|
||||
while (head + tail > static_cast<size_t>(mf + mb)) {
|
||||
std::string cand = s.substr(0, head) + ell + s.substr(s.size() - tail);
|
||||
if (meas(cand) <= maxW) return cand;
|
||||
if (head >= tail) --head; else --tail;
|
||||
}
|
||||
return floor;
|
||||
};
|
||||
for (size_t len : {8u, 20u, 77u, 78u, 79u}) {
|
||||
std::string s(len, 'a');
|
||||
for (size_t i = 0; i < len; ++i) s[i] = static_cast<char>('a' + (i * 7) % 26);
|
||||
for (float maxW = -1; maxW < 700; maxW += 13.0f)
|
||||
EXPECT_EQ(dragonx::util::truncateMiddleToFit(s, 5, 5, maxW, meas), oldFit(s, 5, 5, maxW));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
@@ -18365,6 +18401,7 @@ int main()
|
||||
testLocalTime();
|
||||
testChatThreadLayout();
|
||||
testTextFit();
|
||||
testContactsTextHelpers();
|
||||
testTextFitBanner();
|
||||
testLoadLanguageFromLooseFiles();
|
||||
testWriteFileIfDifferent();
|
||||
|
||||
Reference in New Issue
Block a user