132 lines
14 KiB
Markdown
132 lines
14 KiB
Markdown
# Ссылки в сообщениях
|
||
|
||
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 открывает краткую подсказку по тегам и блокам кода.
|
||
|
||
````text
|
||
**Пример:**
|
||
```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](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.
|
||
Оба разбора не открывают ссылки; некорректный пользовательский текст не создаёт логов.
|
||
|
||
```bash
|
||
cd tools/chatgui-android
|
||
./gradlew :app:testDebugUnitTest --tests com.utun.chat.data.MessageLinksTest
|
||
```
|
||
|
||
`tests/test_message_links.kt` остаётся отдельным CLI-тестом того же TSV;
|
||
ему передаётся путь к файлу, а в classpath нужны собранные классы `MessageLinks`.
|