14 KiB
Ссылки в сообщениях
Android и Qt распознают http://, https:// и utun:// в обычном тексте
личных и групповых сообщений. HTTP/HTTPS нечувствительны к регистру схемы;
utun:// распознаётся только в нижнем регистре. Утилиты разбора не открывают ссылки.
Ссылки выделяются цветом и подчёркиванием; длинные веб-адреса переносятся по ширине.
Сокращённая строка структурированного PM-приглашения сохраняет полный utun:// адрес.
Пробелы, управляющие символы, кавычки и угловые скобки отделяют ссылку от текста.
Конечные . , ; : ! ? и лишние закрывающие скобки удаляются из распознанного адреса.
Сбалансированные скобки в адресе, параметры, fragment и Unicode сохраняются.
Адреса без схемы не распознаются. Некорректные веб-адреса остаются обычным текстом.
Исходное сообщение в БД и протоколе не меняется.
Форматирование сообщений в desktop
Qt-клиент поддерживает **жирный**, __курсив__, ~~зачёркнутый~~ и блоки кода.
Парные маркеры работают внутри одной строки, без вложенности; между маркером и текстом
не должно быть пробела. Маркеры внутри слова не открывают форматирование.
Незакрытые маркеры остаются буквально. Обратная косая черта экранирует *, _, ~,
обратную кавычку и саму себя. Внутри URL escapes и открывающие маркеры не обрабатываются.
Открывающая строка кода содержит ровно три обратные кавычки и необязательное имя языка,
например cpp, c, python, py, js, json, bash, sql.
До/после кавычек и вокруг имени языка допустимы пробелы и табуляции: ``` cpp тоже работает.
Закрывающая строка содержит три обратные кавычки с необязательными пробелами вокруг. Начало/конец сообщения также
считаются границами строк; принимаются LF и CRLF. Несколько блоков в сообщении допустимы.
Пробелы внутри самого кода сохраняются. Кнопка ? справа от Join открывает краткую подсказку по тегам и блокам кода.
**Пример:**
```cpp
int value = 42;
```
Код рисуется моноширинным шрифтом на отдельном фоне; пробелы, табуляции и пустые строки
сохраняются. Длинные строки визуально переносятся без изменения копируемого текста.
Над каждым блоком находится кнопка Copy: копирует весь код без ограждений и метки языка,
включая пробелы, табуляции и пустые строки. Перетаскивание не активирует кнопку.
После копирования кнопка на 1,5 секунды подсвечивается и показывает «Скопировано»;
повторное нажатие продлевает подтверждение. Размер кнопки при этом не меняется.
В шапке слева показано имя указанного/распознанного языка (например, C++ или JavaScript), справа — Copy.
Если язык определить не удалось, показано text; неизвестная явная метка показывается как написана в ограждении.
Внутри кода нет форматирования маркерами, кликабельных ссылок и анимированных emoji.
При пустом языке небольшой модуль tools/chatgui/src/code_language.cpp определяет C, C++, Java,
Kotlin, Rust, PHP, JavaScript, CSS или HTML по содержимому. Явный язык имеет приоритет;
неизвестное имя и text/plain отключают автоопределение и оставляют код без раскраски.
При слабых или противоречивых признаках также остаётся обычный моноширинный текст.
Подсветки синтаксических ошибок нет.
Определитель анализирует только первые 16384 символа UTF-16, исключает комментарии и строковые
литералы и складывает веса характерных конструкций. Каждый признак учитывается один раз;
для выбора нужны оценка не ниже 6 и отрыв от второго кандидата не меньше 3.
Заголовки PHP/HTML и структурные HTML-теги имеют приоритет над вложенным кодом;
конструкции Rust отличают его std:: от C++. Новых зависимостей нет.
Результат подсветки попадает в общий кеш сообщения. Лог general=debug показывает выбранный язык,
оценку, конкурирующую оценку и размер выборки, без самого текста.
Проверка на скачанных исходниках описана в code_language_detection.md.
Исходник остаётся в MsgTextRole, БД и протоколе; редактирование получает маркеры как были.
Выделение и Ctrl+C используют видимый текст без маркеров, в том числе при выборе нескольких
сообщений. Структурированные PM-приглашения сохраняют прежнее сокращение ссылки с копированием
полного адреса; разметка их автоматически сформированного заголовка не применяется.
Медиа и текст опросов не интерпретируются как размеченные сообщения. Android пока показывает исходные маркеры.
src/message_text.cpp в Qt-каталоге готовит видимый текст, стили и ссылки вне кода;
KSyntaxHighlighting возвращает цвета языковых токенов для светлой и тёмной темы.
Цвета не меняют метрики шрифта. Результат кешируется в GUI-потоке с бюджетом 8 МиБ;
изменение текста автоматически выбирает новый ключ. В форматированных сообщениях размеры,
рисование, координаты ссылок, emoji и выделение используют одинаковый QTextLayout.
Обычные сообщения сохраняют прежнюю отрисовку. Диагностика general=debug содержит длины,
число стилей/блоков/токенов и границы выделения, без содержимого сообщений.
Зависимость сборки: KF6SyntaxHighlighting для Qt 6, KF5SyntaxHighlighting для Qt 5.
Ubuntu/Debian: sudo apt-get install libkf6syntaxhighlighting-dev (для Qt 5 — пакет libkf5syntaxhighlighting-dev).
Ubuntu dev-пакет также требует инструменты Qt QML; само приложение связывается только с библиотекой подсветки,
которая использует уже подключённые Qt Core/Gui/Network.
В MSYS2 UCRT64 build.sh вызывает install_deps_win.sh: отсутствующие пакеты компилятора,
CMake/Ninja, Qt 6, OpenSSL, zlib и mingw-w64-ucrt-x86_64-syntax-highlighting устанавливаются
одним вызовом pacman -S --needed. Готовое окружение не требует сети.
DLL подсветки и её лицензии попадают в установщик через существующий обход PE-зависимостей.
Проверки: test_message_text — разбор, реальные определения C++, многострочное состояние,
неизвестный язык, кеш и неизменные метрики при смене темы; test_invite_links — рисование,
drag/copy в обе стороны, ссылки вне кода и сохранность исходника; test_windows_installer —
установка только отсутствующих пакетов, отказ при ошибке установки и комплект DLL без QML.
Распознавание и открытие ссылок
У utun:// распознаётся кандидат из обычного base64-алфавита с до двух конечных =.
На этом этапе не проверяются версия, поля и содержимое приглашения: utun://abc+/==
может получить подсветку, но быть отклонённым при открытии. Предпросмотр Qt и Android
использует общий src/chat/invite_link.c через Qt-обёртку/JNI; принимается версия 0x03.
Сам декодер не проверяет регистрацию/срок join_key и не даёт членства.
Разборщики возвращают непересекающиеся диапазоны исходного текста в порядке появления
и полные адреса в исходном написании: Qt — start/length, Android — start/end,
где end исключён. Единицы обоих API — UTF-16:
tools/chatgui/src/message_links.cpp, Android data/MessageLinks.kt.
Удаление внешней пунктуации занимает линейное время: баланс трёх видов скобок
считается один раз, хвост сокращается по индексу без повторного копирования адреса.
Порт веб-адреса пустой либо десятичный от 0 до 65535; знак перед числом недопустим.
Подсветка и нажатие используют эти диапазоны. Android подставляет emoji placeholders
только в обычный текст между ссылками: emoji внутри адреса остаётся частью полного URL.
Qt использует один QTextLayout для размеров, отрисовки, выделения и координат нажатия.
Перетаскивание выделяет текст без открытия ссылки.
HTTP/HTTPS открываются из GUI системным обработчиком: Android ACTION_VIEW,
Qt QDesktopServices::openUrl. На нажатии веб-адрес снова проверяется.
В Qt utun:// сначала декодируется: уже существующая группа, где узел является
участником, открывается без JOIN; иначе показывается JoinDialog с автоматической попыткой входа.
На Android адрес передаётся в существующий invite/JOIN flow.
Ошибки открытия показываются через перевод интерфейса и логируются.
Диагностика открытия содержит схему и host, без полного пути/query.
Общие случаи: tools/chatgui/tests/message_links_cases.tsv.
Qt test_invite_links проверяет разбор, перенесённые веб-ссылки, смесь схем,
нажатие, drag/copy и сокращённые приглашения; test_emoji_playback — жесты приглашений.
Android MessageLinksTest проверяет тот же TSV через Gradle, некорректные authority
и длинный хвост пунктуации. Аналогичный тест длинного хвоста есть в Qt.
Оба разбора не открывают ссылки; некорректный пользовательский текст не создаёт логов.
cd tools/chatgui-android
./gradlew :app:testDebugUnitTest --tests com.utun.chat.data.MessageLinksTest
tests/test_message_links.kt остаётся отдельным CLI-тестом того же TSV;
ему передаётся путь к файлу, а в classpath нужны собранные классы MessageLinks.