Something went wrong. Try again.
The agentic engineering control plane for the posthuman future
Something went wrong. Try again.
7.5 kB · 257 lines
C++
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258#include "LinkScan.h"
#include <algorithm>#include <utility>
#include <QChar>#include <QDir>
namespace {
constexpr QLatin1String kPathChars = QLatin1String("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123" "456789_./~+@%#=:");
bool isUriStartChar(QChar ch) { return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');}
bool isSchemeChar(QChar ch) { return isUriStartChar(ch) || (ch >= '0' && ch <= '9') || ch == '+' || ch == '.' || ch == '-';}
bool isUriBodyChar(QChar ch) { static const QString forbidden = QStringLiteral("<>\"'`"); return !ch.isSpace() && !forbidden.contains(ch);}
bool isPathChar(QChar ch) { return kPathChars.contains(ch);}
// One URI match: `scheme://body` or `mailto:body`. Returns -1 when no URI// starts at `from`.int uriEndAt(const QString& text, int from) { const int len = static_cast<int>(text.size()); int i = from; if (!isUriStartChar(text.at(i))) { return -1; } ++i; while (i < len && isSchemeChar(text.at(i))) { ++i; } const QStringView scheme(text.constData() + from, i - from); const bool doubleSlash = i + 1 < len && text.at(i) == ':' && text.at(i + 1) == '/' && i + 2 < len && text.at(i + 2) == '/'; const bool mailto = scheme == QLatin1String("mailto") && i < len && text.at(i) == ':'; if (!doubleSlash && !mailto) { return -1; } ++i; // the ':' if (doubleSlash) { i += 2; // the '//' } while (i < len && isUriBodyChar(text.at(i))) { ++i; } return i;}
bool isTrimPunct(QChar ch) { static const QString punct = QStringLiteral(".,;:!?"); return punct.contains(ch);}
// True when the trailing closer of `text` has no opener left to balance it.// Only that case trims; a closer that matches an opening bracket inside the// token stays. The caller guarantees the last character is `close`.bool trailingCloserUnmatched(const QString& text, QChar open, QChar close) { int depth = 0; const QStringView body = QStringView(text).first(text.size() - 1); for (const QChar ch : body) { if (ch == open) { ++depth; } else if (ch == close && depth > 0) { --depth; } } return depth == 0;}
// A trailing closer is trimmed only when it has no opener to balance.bool unmatchedCloserAtEnd(const QString& text) { const QChar last = text.back(); if (last == ')') { return trailingCloserUnmatched(text, '(', ')'); } if (last == ']') { return trailingCloserUnmatched(text, '[', ']'); } if (last == '}') { return trailingCloserUnmatched(text, '{', '}'); } return false;}
// Trim trailing punctuation: .,;:!? an unmatched ) ] }, and a trailing ' or ".// Keeps a balanced closer and a trailing '/'.int trimEnd(const QString& token) { int end = static_cast<int>(token.size()); while (end > 0) { const QChar last = token.at(end - 1); if (isTrimPunct(last) || last == '\'' || last == '"' || unmatchedCloserAtEnd(token.left(end))) { --end; continue; } break; } return end;}
} // namespace
QVector<ScannedLink> scanRowLinks(const QString& text, const QVector<int>& columnOfChar) { QVector<ScannedLink> links; const int len = static_cast<int>(text.size()); if (len == 0) { return links; }
// Pass 1: URIs. `i` is a cursor that resumes past each match, so this is // the hand-written-iteration exception; the match ranges let pass 2 skip // them. QVector<std::pair<int, int>> uriRanges; int i = 0; while (i < len) { if (isUriStartChar(text.at(i))) { const int end = uriEndAt(text, i); if (end > i) { uriRanges.append({i, end}); i = end; continue; } } ++i; }
const auto inUri = [&uriRanges](int pos) { return std::ranges::any_of( uriRanges, [pos](const std::pair<int, int>& range) { return pos >= range.first && pos < range.second; }); };
for (const auto& [s, e] : uriRanges) { const int end = trimEnd(text.mid(s, e - s)) + s; if (end <= s) { continue; } ScannedLink link; link.startCol = columnOfChar.value(s); link.endCol = columnOfChar.value(end - 1); link.text = text.mid(s, end - s); link.isUri = true; links.append(link); }
// Pass 2: paths — a run of path characters containing '/', skipping URI // ranges. `i` is again the cursor: it resumes at each match's end, so tokens // never overlap. i = 0; while (i < len) { if (inUri(i) || !isPathChar(text.at(i))) { ++i; continue; } int j = i; while (j < len && !inUri(j) && isPathChar(text.at(j))) { ++j; } const QStringView run(text.constData() + i, j - i); if (run.contains('/')) { const int end = trimEnd(run.toString()) + i; if (end > i) { const QString token = text.mid(i, end - i); // Anchored paths only: absolute, home-relative, or explicitly // relative. A bare relative chain such as app/src/main.cpp is how // source code, grep results, diff headers, and log lines spell // module and target names, so on its own it is not a filesystem // path and must not become a link. The consumer still requires the // resolved path to exist, so this only removes false positives. const bool anchored = token.startsWith(QLatin1Char('/')) || token.startsWith(QLatin1String("~/")) || token.startsWith(QLatin1String("./")) || token.startsWith(QLatin1String("../")); if (!anchored) { i = j; continue; } ScannedLink link; link.startCol = columnOfChar.value(i); link.endCol = columnOfChar.value(end - 1); link.text = token; link.isUri = false; links.append(link); i = end; continue; } } i = j; }
return links;}
PathToken splitLineColumn(const QString& token) { PathToken result; // A colon starts a suffix only when digits follow it. const auto candidates = std::views::iota(qsizetype{0}, token.size() - 1); const auto colon = std::ranges::find_if(candidates, [&token](qsizetype i) { return token.at(i) == QLatin1Char(':') && token.at(i + 1).isDigit(); }); if (colon == candidates.end()) { result.path = token; return result; } result.path = token.left(*colon); const QStringView rest = QStringView(token).mid(*colon + 1); const int secondColon = static_cast<int>(rest.indexOf(':')); const QStringView linePart = secondColon < 0 ? rest : rest.left(secondColon); result.line = linePart.toInt(); if (secondColon >= 0) { const QStringView colPart = rest.mid(secondColon + 1); bool allDigits = !colPart.isEmpty(); for (const QChar ch : colPart) { if (!ch.isDigit()) { allDigits = false; break; } } if (allDigits) { result.column = colPart.toInt(); } } return result;}
QString resolveDisplayedPath(const QString& path, const QString& baseDir) { QString expanded = path; if (expanded == QLatin1String("~") || expanded.startsWith(QLatin1String("~/"))) { expanded = QDir::homePath() + expanded.mid(1); } if (QDir::isAbsolutePath(expanded)) { return QDir::cleanPath(expanded); } if (baseDir.isEmpty()) { return expanded; } return QDir::cleanPath(baseDir + QLatin1Char('/') + expanded);}