#include "TerminalSearch.h" QVector findAllMatches(const QVector& rows, const QString& query) { QVector matches; const QString needle = query.trimmed(); if (needle.isEmpty()) { return matches; } const qsizetype needleLen = needle.length(); for (const SearchRow& row : rows) { if (row.text.isEmpty()) { continue; } // Case-insensitive plain substring scan. from advances past each hit so // overlapping occurrences are all reported left to right. qsizetype from = 0; while (true) { const qsizetype hit = row.text.indexOf(needle, from, Qt::CaseInsensitive); if (hit < 0) { break; } // UTF-16 offset -> cell column. columnOfChar (when populated) maps // every unit to its grid column, so wide graphemes keep the span // honest; without it the offset IS the column. const int startCol = row.columnOfChar.isEmpty() ? static_cast(hit) : row.columnOfChar[static_cast(hit)]; const qsizetype lastUnit = hit + needleLen - 1; const int endColIncl = row.columnOfChar.isEmpty() ? static_cast(lastUnit) : row.columnOfChar[static_cast(lastUnit)]; matches.append(SearchMatch{.row = row.row, .startCol = startCol, .endCol = endColIncl}); from = hit + 1; } } return matches; } int wrapStepMatch(int current, int delta, int count) { if (count <= 0) { return -1; } if (current < 0 || current >= count) { // No valid selection: step in lands on the first (next) or last // (previous) match. return delta >= 0 ? 0 : count - 1; } const int next = current + delta; if (next < 0) { return count - 1; } if (next >= count) { return 0; } return next; }