Фаза 6: просмотр Markdown — MdParser (cmark-gfm AST→HTML: таблицы/ссылки/вики/картинки data-URI/задачи-чекбоксы), подсветка кода KSyntaxHighlighting (алиасы «С»/«С++»/python), RichText-просмотр в EditorPane (onLinkActivated); тест mdparser (15/15)
This commit is contained in:
@@ -41,6 +41,7 @@ set(FLORA_HEADERS
|
|||||||
src/core/FileStore.h
|
src/core/FileStore.h
|
||||||
src/core/SettingsStore.h
|
src/core/SettingsStore.h
|
||||||
src/git/GitManager.h
|
src/git/GitManager.h
|
||||||
|
src/markdown/MdParser.h
|
||||||
src/sync/SyncManager.h
|
src/sync/SyncManager.h
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ set(FLORA_SOURCES
|
|||||||
src/core/FileStore.cpp
|
src/core/FileStore.cpp
|
||||||
src/core/SettingsStore.cpp
|
src/core/SettingsStore.cpp
|
||||||
src/git/GitManager.cpp
|
src/git/GitManager.cpp
|
||||||
|
src/markdown/MdParser.cpp
|
||||||
src/sync/SyncManager.cpp
|
src/sync/SyncManager.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,6 +63,9 @@ add_library(flora_core STATIC
|
|||||||
)
|
)
|
||||||
target_include_directories(flora_core PUBLIC src)
|
target_include_directories(flora_core PUBLIC src)
|
||||||
target_compile_features(flora_core PUBLIC cxx_std_17)
|
target_compile_features(flora_core PUBLIC cxx_std_17)
|
||||||
|
target_compile_definitions(flora_core PRIVATE
|
||||||
|
CMARK_GFM_STATIC_DEFINE
|
||||||
|
CMARK_GFM_EXTENSIONS_STATIC_DEFINE)
|
||||||
target_link_libraries(flora_core PUBLIC
|
target_link_libraries(flora_core PUBLIC
|
||||||
Qt6::Quick
|
Qt6::Quick
|
||||||
Qt6::QuickControls2
|
Qt6::QuickControls2
|
||||||
@@ -73,6 +78,9 @@ target_link_libraries(flora_core PUBLIC
|
|||||||
Qt6::Svg
|
Qt6::Svg
|
||||||
cmark::cmark
|
cmark::cmark
|
||||||
)
|
)
|
||||||
|
if(TARGET cmark::cmark_extensions)
|
||||||
|
target_link_libraries(flora_core PUBLIC cmark::cmark_extensions)
|
||||||
|
endif()
|
||||||
if(TARGET Flora_libgit2_static)
|
if(TARGET Flora_libgit2_static)
|
||||||
target_link_libraries(flora_core PUBLIC Flora_libgit2_static)
|
target_link_libraries(flora_core PUBLIC Flora_libgit2_static)
|
||||||
else()
|
else()
|
||||||
@@ -81,6 +89,9 @@ endif()
|
|||||||
if(TARGET libssh2_static)
|
if(TARGET libssh2_static)
|
||||||
target_link_libraries(flora_core PUBLIC libssh2_static)
|
target_link_libraries(flora_core PUBLIC libssh2_static)
|
||||||
endif()
|
endif()
|
||||||
|
if(TARGET KF6SyntaxHighlighting)
|
||||||
|
target_link_libraries(flora_core PUBLIC KF6SyntaxHighlighting)
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- Приложение (QML) ---
|
# --- Приложение (QML) ---
|
||||||
qt_add_executable(flora src/main.cpp)
|
qt_add_executable(flora src/main.cpp)
|
||||||
|
|||||||
@@ -237,18 +237,61 @@ Item {
|
|||||||
id: viewer
|
id: viewer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: !pane.editMode
|
visible: !pane.editMode
|
||||||
text: documentManager.contentAt(pane.docIndex)
|
textFormat: TextEdit.RichText
|
||||||
|
text: ""
|
||||||
readOnly: true
|
readOnly: true
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
font.family: "Menlo, Consolas, 'DejaVu Sans Mono', monospace"
|
|
||||||
font.pixelSize: 14
|
|
||||||
wrapMode: TextEdit.Wrap
|
wrapMode: TextEdit.Wrap
|
||||||
padding: 12
|
padding: 12
|
||||||
|
onLinkActivated: (href) => pane.handleViewLink(href)
|
||||||
placeholderText: qsTr("Документ не выбран")
|
placeholderText: qsTr("Документ не выбран")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- Просмотр (режим просмотра) ----------
|
||||||
|
function pathDir(p) {
|
||||||
|
const idx = p.lastIndexOf("/")
|
||||||
|
return idx >= 0 ? p.substring(0, idx) : p
|
||||||
|
}
|
||||||
|
function renderView() {
|
||||||
|
if (pane.editMode || pane.docIndex < 0)
|
||||||
|
return
|
||||||
|
viewer.text = mdParser.render(documentManager.contentAt(pane.docIndex),
|
||||||
|
fileStore.vault,
|
||||||
|
pathDir(documentManager.pathAt(pane.docIndex)))
|
||||||
|
}
|
||||||
|
function handleViewLink(href) {
|
||||||
|
if (href.startsWith("flora://task/")) {
|
||||||
|
const line = Number(href.substring("flora://task/".length))
|
||||||
|
if (line > 0) pane.toggleTaskLine(line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (href.startsWith("flora://open/")) {
|
||||||
|
const path = decodeURIComponent(href.substring("flora://open/".length))
|
||||||
|
if (path.length > 0) documentManager.openDocument(path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (href.startsWith("flora://wiki/")) {
|
||||||
|
// Фаза 8: создание через базу связей; пока — статус.
|
||||||
|
console.log("wiki:", decodeURIComponent(href))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log("внешняя ссылка:", href)
|
||||||
|
}
|
||||||
|
function toggleTaskLine(line) {
|
||||||
|
const text = documentManager.contentAt(pane.docIndex)
|
||||||
|
const lines = text.split("\n")
|
||||||
|
const ln = lines[line - 1]
|
||||||
|
if (ln === undefined) return
|
||||||
|
if (/\[ \]/.test(ln))
|
||||||
|
lines[line - 1] = ln.replace("[ ]", "[x]")
|
||||||
|
else if (/\[x\]/.test(ln))
|
||||||
|
lines[line - 1] = ln.replace("[x]", "[ ]")
|
||||||
|
documentManager.updateContent(pane.docIndex, lines.join("\n"))
|
||||||
|
pane.renderView()
|
||||||
|
}
|
||||||
|
|
||||||
// Свежий заголовок при смене признака «изменено»
|
// Свежий заголовок при смене признака «изменено»
|
||||||
Connections {
|
Connections {
|
||||||
target: documentManager
|
target: documentManager
|
||||||
@@ -262,10 +305,17 @@ Item {
|
|||||||
target: documentManager
|
target: documentManager
|
||||||
function onContentChanged(i) {
|
function onContentChanged(i) {
|
||||||
if (i === pane.docIndex && !pane.editMode)
|
if (i === pane.docIndex && !pane.editMode)
|
||||||
viewer.text = documentManager.contentAt(i)
|
pane.renderView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDocIndexChanged: pane.refreshHeader()
|
onDocIndexChanged: {
|
||||||
|
pane.refreshHeader()
|
||||||
|
pane.renderView()
|
||||||
|
}
|
||||||
|
onEditModeChanged: {
|
||||||
|
if (!pane.editMode)
|
||||||
|
pane.renderView()
|
||||||
|
}
|
||||||
Component.onCompleted: pane.refreshHeader()
|
Component.onCompleted: pane.refreshHeader()
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "core/DocumentManager.h"
|
#include "core/DocumentManager.h"
|
||||||
#include "core/SettingsStore.h"
|
#include "core/SettingsStore.h"
|
||||||
#include "git/GitManager.h"
|
#include "git/GitManager.h"
|
||||||
|
#include "markdown/MdParser.h"
|
||||||
#include "sync/SyncManager.h"
|
#include "sync/SyncManager.h"
|
||||||
|
|
||||||
#include <cmark-gfm.h>
|
#include <cmark-gfm.h>
|
||||||
@@ -39,6 +40,7 @@ int main(int argc, char *argv[])
|
|||||||
SyncManager syncManager;
|
SyncManager syncManager;
|
||||||
VaultManager vaultManager(&settingsStore, &gitManager);
|
VaultManager vaultManager(&settingsStore, &gitManager);
|
||||||
DocumentManager documentManager;
|
DocumentManager documentManager;
|
||||||
|
MdParser mdParser;
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
engine.rootContext()->setContextProperty("settingsStore", &settingsStore);
|
engine.rootContext()->setContextProperty("settingsStore", &settingsStore);
|
||||||
@@ -46,6 +48,7 @@ int main(int argc, char *argv[])
|
|||||||
engine.rootContext()->setContextProperty("syncManager", &syncManager);
|
engine.rootContext()->setContextProperty("syncManager", &syncManager);
|
||||||
engine.rootContext()->setContextProperty("vaultManager", &vaultManager);
|
engine.rootContext()->setContextProperty("vaultManager", &vaultManager);
|
||||||
engine.rootContext()->setContextProperty("documentManager", &documentManager);
|
engine.rootContext()->setContextProperty("documentManager", &documentManager);
|
||||||
|
engine.rootContext()->setContextProperty("mdParser", &mdParser);
|
||||||
|
|
||||||
WindowController windowController(
|
WindowController windowController(
|
||||||
&engine, QStringLiteral("qrc:/qt/qml/Flora/qml/MainWindow.qml"));
|
&engine, QStringLiteral("qrc:/qt/qml/Flora/qml/MainWindow.qml"));
|
||||||
|
|||||||
@@ -0,0 +1,497 @@
|
|||||||
|
#include "markdown/MdParser.h"
|
||||||
|
|
||||||
|
#include <cmark-gfm.h>
|
||||||
|
#include <cmark-gfm-extension_api.h>
|
||||||
|
#include <cmark-gfm-core-extensions.h>
|
||||||
|
#include "table.h"
|
||||||
|
#include "strikethrough.h"
|
||||||
|
|
||||||
|
#include <KSyntaxHighlighting/abstracthighlighter.h>
|
||||||
|
#include <KSyntaxHighlighting/definition.h>
|
||||||
|
#include <KSyntaxHighlighting/format.h>
|
||||||
|
#include <KSyntaxHighlighting/repository.h>
|
||||||
|
#include <KSyntaxHighlighting/state.h>
|
||||||
|
#include <KSyntaxHighlighting/theme.h>
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QImageReader>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
using KSyntaxHighlighting::AbstractHighlighter;
|
||||||
|
using KSyntaxHighlighting::Definition;
|
||||||
|
using KSyntaxHighlighting::Format;
|
||||||
|
using KSyntaxHighlighting::Repository;
|
||||||
|
using KSyntaxHighlighting::State;
|
||||||
|
using KSyntaxHighlighting::Theme;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
QString esc(const QString &s)
|
||||||
|
{
|
||||||
|
QString out;
|
||||||
|
out.reserve(s.size());
|
||||||
|
for (const QChar &c : s) {
|
||||||
|
switch (c.unicode()) {
|
||||||
|
case '&': out += "&"; break;
|
||||||
|
case '<': out += "<"; break;
|
||||||
|
case '>': out += ">"; break;
|
||||||
|
case '"': out += """; break;
|
||||||
|
default: out += c; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString fileHrefFor(const QString &path)
|
||||||
|
{
|
||||||
|
const QByteArray enc = QUrl::toPercentEncoding(QDir::cleanPath(path));
|
||||||
|
return QStringLiteral("flora://open/") + QString::fromLatin1(enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString wikiHrefFor(const QString &name)
|
||||||
|
{
|
||||||
|
const QByteArray enc = QUrl::toPercentEncoding(name);
|
||||||
|
return QStringLiteral("flora://wiki/") + QString::fromLatin1(enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Картинка → data: URI (если получилось прочитать), иначе "".
|
||||||
|
QString imageDataUri(const QString &path)
|
||||||
|
{
|
||||||
|
QFile f(path);
|
||||||
|
if (!f.open(QIODevice::ReadOnly) || f.size() > 3 * 1024 * 1024)
|
||||||
|
return QString();
|
||||||
|
const QByteArray data = f.readAll();
|
||||||
|
QImageReader reader(path);
|
||||||
|
const QByteArray fmt = reader.format().toLower();
|
||||||
|
const QString mime = fmt == "jpg" || fmt == "jpeg" ? QStringLiteral("image/jpeg")
|
||||||
|
: fmt == "png" ? QStringLiteral("image/png")
|
||||||
|
: fmt == "gif" ? QStringLiteral("image/gif")
|
||||||
|
: fmt == "svg" ? QStringLiteral("image/svg+xml")
|
||||||
|
: fmt == "webp" ? QStringLiteral("image/webp")
|
||||||
|
: fmt == "bmp" ? QStringLiteral("image/bmp")
|
||||||
|
: QString();
|
||||||
|
if (mime.isEmpty())
|
||||||
|
return QString();
|
||||||
|
return QStringLiteral("data:%1;base64,%2")
|
||||||
|
.arg(mime, QString::fromLatin1(data.toBase64()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сопоставление имени из ограждения с языками KSyntaxHighlighting.
|
||||||
|
QString normalizeLang(QString l)
|
||||||
|
{
|
||||||
|
l = l.trimmed();
|
||||||
|
if (l.isEmpty())
|
||||||
|
return l;
|
||||||
|
struct Map { const char *from; const char *to; };
|
||||||
|
static const Map maps[] = {
|
||||||
|
{"С", "C"}, {"С++", "C++"}, {"с", "C"}, {"с++", "C++"},
|
||||||
|
{"python", "Python"}, {"py", "Python"},
|
||||||
|
{"javascript", "JavaScript"}, {"js", "JavaScript"}, {"ts", "TypeScript"},
|
||||||
|
{"golang", "Go"}, {"go", "Go"}, {"rust", "Rust"},
|
||||||
|
{"shell", "Bash"}, {"bash", "Bash"}, {"sh", "Bash"},
|
||||||
|
{"csharp", "C#"}, {"c#", "C#"},
|
||||||
|
{"jav", "Java"}, {"json", "JSON"}, {"yaml", "YAML"}, {"yml", "YAML"},
|
||||||
|
{"html", "HTML"}, {"css", "CSS"}, {"xml", "XML"}, {"sql", "SQL"},
|
||||||
|
{"markdown", "Markdown"}, {"md", "Markdown"},
|
||||||
|
{"text", "Plain Text"}, {"txt", "Plain Text"}
|
||||||
|
};
|
||||||
|
for (const Map &m : maps) {
|
||||||
|
if (l == QString::fromUtf8(m.from))
|
||||||
|
return QString::fromUtf8(m.to);
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Накапливает подсвеченные фрагменты кода.
|
||||||
|
class CodeHighlighter : public AbstractHighlighter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit CodeHighlighter(Repository *repo)
|
||||||
|
: m_repo(repo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyFormat(int offset, int length, const Format &format) override
|
||||||
|
{
|
||||||
|
if (offset < 0 || length <= 0)
|
||||||
|
return;
|
||||||
|
Segment seg;
|
||||||
|
seg.start = offset;
|
||||||
|
seg.length = length;
|
||||||
|
const Theme th = theme();
|
||||||
|
seg.color = format.hasTextColor(th)
|
||||||
|
? format.textColor(th).name() : QString();
|
||||||
|
seg.bg = format.hasBackgroundColor(th)
|
||||||
|
? format.backgroundColor(th).name() : QString();
|
||||||
|
seg.bold = format.isBold(th);
|
||||||
|
m_segments.append(seg);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString highlight(const QString &code, const QString &lang)
|
||||||
|
{
|
||||||
|
m_segments.clear();
|
||||||
|
Definition def = m_repo->definitionForName(lang);
|
||||||
|
if (!def.isValid())
|
||||||
|
return esc(code);
|
||||||
|
setDefinition(def);
|
||||||
|
setTheme(m_repo->defaultTheme());
|
||||||
|
|
||||||
|
QStringList outLines;
|
||||||
|
const QStringList lines = code.split(QLatin1Char('\n'));
|
||||||
|
State state;
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
m_segments.clear();
|
||||||
|
state = highlightLine(QStringView(line), state);
|
||||||
|
outLines.append(buildLine(line));
|
||||||
|
}
|
||||||
|
return outLines.join(QLatin1Char('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Segment {
|
||||||
|
int start = 0;
|
||||||
|
int length = 0;
|
||||||
|
QString color;
|
||||||
|
QString bg;
|
||||||
|
bool bold = false;
|
||||||
|
};
|
||||||
|
QVector<Segment> m_segments;
|
||||||
|
Repository *m_repo;
|
||||||
|
|
||||||
|
QString buildLine(const QString &line)
|
||||||
|
{
|
||||||
|
if (m_segments.isEmpty())
|
||||||
|
return esc(line);
|
||||||
|
QString html;
|
||||||
|
int pos = 0;
|
||||||
|
for (const Segment &seg : m_segments) {
|
||||||
|
if (seg.start > pos)
|
||||||
|
html += esc(line.mid(pos, seg.start - pos));
|
||||||
|
QString style;
|
||||||
|
if (!seg.color.isEmpty())
|
||||||
|
style += QStringLiteral("color:%1;").arg(seg.color);
|
||||||
|
if (!seg.bg.isEmpty())
|
||||||
|
style += QStringLiteral("background-color:%1;").arg(seg.bg);
|
||||||
|
if (seg.bold)
|
||||||
|
style += QStringLiteral("font-weight:bold;");
|
||||||
|
const QString body = esc(line.mid(seg.start, seg.length));
|
||||||
|
html += QStringLiteral("<span style=\"%1\">%2</span>").arg(style, body);
|
||||||
|
pos = seg.start + seg.length;
|
||||||
|
}
|
||||||
|
if (pos < line.size())
|
||||||
|
html += esc(line.mid(pos));
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct MdParser::Impl
|
||||||
|
{
|
||||||
|
Impl()
|
||||||
|
{
|
||||||
|
// Реестр языков и тем KSyntaxHighlighting
|
||||||
|
m_repo = new Repository;
|
||||||
|
}
|
||||||
|
~Impl() { delete m_repo; }
|
||||||
|
|
||||||
|
Repository *m_repo = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
MdParser::MdParser(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m(new Impl)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class Renderer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Renderer(Repository *repo, const QString &vaultRoot, const QString &fileDir)
|
||||||
|
: m_repo(repo)
|
||||||
|
, m_vaultRoot(vaultRoot)
|
||||||
|
, m_fileDir(fileDir)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString renderTree(cmark_node *node)
|
||||||
|
{
|
||||||
|
outer = node;
|
||||||
|
return block(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Repository *m_repo;
|
||||||
|
QString m_vaultRoot;
|
||||||
|
QString m_fileDir;
|
||||||
|
cmark_node *outer = nullptr;
|
||||||
|
|
||||||
|
QString childrenHtml(cmark_node *parent, bool blockMode = true)
|
||||||
|
{
|
||||||
|
QString out;
|
||||||
|
for (cmark_node *c = cmark_node_first_child(parent); c; c = cmark_node_next(c)) {
|
||||||
|
if (blockMode) {
|
||||||
|
// мягкий разрыв внутри списков не плодим
|
||||||
|
if (cmark_node_get_type(c) == CMARK_NODE_ITEM)
|
||||||
|
out += list_item(c);
|
||||||
|
else
|
||||||
|
out += block(c);
|
||||||
|
} else {
|
||||||
|
out += inlineAll(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString block(cmark_node *n)
|
||||||
|
{
|
||||||
|
const cmark_node_type t = cmark_node_get_type(n);
|
||||||
|
// Таблицы — extern-переменные (нельзя в case)
|
||||||
|
if (t == CMARK_NODE_TABLE || t == CMARK_NODE_TABLE_ROW
|
||||||
|
|| t == CMARK_NODE_TABLE_CELL)
|
||||||
|
return list(n);
|
||||||
|
|
||||||
|
switch (t) {
|
||||||
|
case CMARK_NODE_DOCUMENT:
|
||||||
|
return childrenHtml(n);
|
||||||
|
case CMARK_NODE_BLOCK_QUOTE:
|
||||||
|
return QStringLiteral("<blockquote>") + childrenHtml(n) + QStringLiteral("</blockquote>");
|
||||||
|
case CMARK_NODE_LIST:
|
||||||
|
return list(n);
|
||||||
|
case CMARK_NODE_ITEM:
|
||||||
|
return list_item(n);
|
||||||
|
case CMARK_NODE_HEADING: {
|
||||||
|
const int lvl = cmark_node_get_heading_level(n);
|
||||||
|
return QStringLiteral("<h%1>").arg(lvl)
|
||||||
|
+ childrenHtml(n, false)
|
||||||
|
+ QStringLiteral("</h%1>").arg(lvl);
|
||||||
|
}
|
||||||
|
case CMARK_NODE_CODE_BLOCK:
|
||||||
|
return code_block(n);
|
||||||
|
case CMARK_NODE_THEMATIC_BREAK:
|
||||||
|
return QStringLiteral("<hr/>");
|
||||||
|
case CMARK_NODE_PARAGRAPH:
|
||||||
|
return QStringLiteral("<p>") + childrenHtml(n, false) + QStringLiteral("</p>");
|
||||||
|
case CMARK_NODE_HTML_BLOCK:
|
||||||
|
return QStringLiteral("<p>%1</p>").arg(esc(QString::fromUtf8(cmark_node_get_literal(n))));
|
||||||
|
default:
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString list(cmark_node *n)
|
||||||
|
{
|
||||||
|
const cmark_node_type t = cmark_node_get_type(n);
|
||||||
|
if (t == CMARK_NODE_TABLE) {
|
||||||
|
QString h = QStringLiteral("<table cellspacing=\"0\" style=\"border-collapse:collapse\">");
|
||||||
|
int i = 0;
|
||||||
|
for (cmark_node *row = cmark_node_first_child(n); row; row = cmark_node_next(row), ++i) {
|
||||||
|
h += QStringLiteral("<tr>");
|
||||||
|
for (cmark_node *cell = cmark_node_first_child(row); cell; cell = cmark_node_next(cell)) {
|
||||||
|
const QString tag = i == 0 ? QStringLiteral("th") : QStringLiteral("td");
|
||||||
|
h += QStringLiteral("<%1 style=\"border:1px solid #ccc;padding:2px 8px\">")
|
||||||
|
.arg(tag);
|
||||||
|
h += childrenHtml(cell, false);
|
||||||
|
h += QStringLiteral("</%1>").arg(tag);
|
||||||
|
}
|
||||||
|
h += QStringLiteral("</tr>");
|
||||||
|
}
|
||||||
|
h += QStringLiteral("</table>");
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
if (t == CMARK_NODE_TABLE_ROW || t == CMARK_NODE_TABLE_CELL)
|
||||||
|
return childrenHtml(n, false);
|
||||||
|
|
||||||
|
const cmark_list_type lt = cmark_node_get_list_type(n);
|
||||||
|
const char *tag = (lt == CMARK_ORDERED_LIST) ? "ol" : "ul";
|
||||||
|
QString h = QStringLiteral("<%1>").arg(tag);
|
||||||
|
h += childrenHtml(n);
|
||||||
|
h += QStringLiteral("</%1>").arg(tag);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString list_item(cmark_node *n)
|
||||||
|
{
|
||||||
|
const bool isTask = QString::fromUtf8(cmark_node_get_type_string(n))
|
||||||
|
== QLatin1String("tasklist");
|
||||||
|
QString body;
|
||||||
|
if (isTask) {
|
||||||
|
const bool checked = cmark_gfm_extensions_get_tasklist_item_checked(n);
|
||||||
|
const int startLine = int(cmark_node_get_start_line(n));
|
||||||
|
const QString box = checked
|
||||||
|
? QStringLiteral("<a href=\"flora://task/%1\">\u2611</a>").arg(startLine)
|
||||||
|
: QStringLiteral("<a href=\"flora://task/%1\">\u2610</a>").arg(startLine);
|
||||||
|
body = box + ' ' + childrenHtml(n);
|
||||||
|
} else {
|
||||||
|
body = childrenHtml(n);
|
||||||
|
}
|
||||||
|
return QStringLiteral("<li>") + body + QStringLiteral("</li>");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString code_block(cmark_node *n)
|
||||||
|
{
|
||||||
|
const char *info = cmark_node_get_fence_info(n);
|
||||||
|
const QString lang = normalizeLang(QString::fromUtf8(info ? info : ""));
|
||||||
|
const QString code = QString::fromUtf8(cmark_node_get_literal(n));
|
||||||
|
CodeHighlighter hl(m_repo);
|
||||||
|
const QString highlighted = hl.highlight(code, lang);
|
||||||
|
const QString cls = lang.isEmpty() ? QString() : QStringLiteral(" class=\"language-%1\"").arg(esc(lang));
|
||||||
|
return QStringLiteral("<pre><code%1>%2</code></pre>").arg(cls, highlighted);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString inlineAll(cmark_node *n)
|
||||||
|
{
|
||||||
|
const cmark_node_type t = cmark_node_get_type(n);
|
||||||
|
if (t == CMARK_NODE_STRIKETHROUGH) // extern-переменная — через if, не case
|
||||||
|
return QStringLiteral("<s>") + children(n) + QStringLiteral("</s>");
|
||||||
|
|
||||||
|
switch (t) {
|
||||||
|
case CMARK_NODE_TEXT:
|
||||||
|
return inlineText(n);
|
||||||
|
case CMARK_NODE_SOFTBREAK:
|
||||||
|
return QStringLiteral("\n");
|
||||||
|
case CMARK_NODE_LINEBREAK:
|
||||||
|
return QStringLiteral("<br/>");
|
||||||
|
case CMARK_NODE_CODE:
|
||||||
|
return QStringLiteral("<code style=\"background:#f2f2f2;font-family:Menlo,Consolas,monospace\">%1</code>")
|
||||||
|
.arg(esc(QString::fromUtf8(cmark_node_get_literal(n))));
|
||||||
|
case CMARK_NODE_EMPH:
|
||||||
|
return QStringLiteral("<i>") + children(n) + QStringLiteral("</i>");
|
||||||
|
case CMARK_NODE_STRONG:
|
||||||
|
return QStringLiteral("<b>") + children(n) + QStringLiteral("</b>");
|
||||||
|
case CMARK_NODE_LINK:
|
||||||
|
return linkNode(n);
|
||||||
|
case CMARK_NODE_IMAGE:
|
||||||
|
return imageNode(n);
|
||||||
|
case CMARK_NODE_HTML_INLINE:
|
||||||
|
return esc(QString::fromUtf8(cmark_node_get_literal(n)));
|
||||||
|
case CMARK_NODE_CUSTOM_INLINE:
|
||||||
|
return QString();
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return children(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Специальная обработка текста: вики-ссылки [[ ]].
|
||||||
|
QString inlineText(cmark_node *n)
|
||||||
|
{
|
||||||
|
QString text = QString::fromUtf8(cmark_node_get_literal(n));
|
||||||
|
if (!text.contains(QStringLiteral("[[")))
|
||||||
|
return esc(text);
|
||||||
|
QString out;
|
||||||
|
int i = 0;
|
||||||
|
while (i < text.size()) {
|
||||||
|
const int a = text.indexOf(QStringLiteral("[["), i);
|
||||||
|
if (a < 0) {
|
||||||
|
out += esc(text.mid(i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out += esc(text.mid(i, a - i));
|
||||||
|
const int b = text.indexOf(QStringLiteral("]]"), a + 2);
|
||||||
|
if (b < 0) {
|
||||||
|
out += esc(text.mid(a));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const QString target = text.mid(a + 2, b - a - 2);
|
||||||
|
out += wikiLink(target);
|
||||||
|
i = b + 2;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString wikiLink(const QString &target)
|
||||||
|
{
|
||||||
|
// ищем файл target или target.md в каталоге файла / корне хранилища
|
||||||
|
QStringList cands;
|
||||||
|
cands << m_fileDir + '/' + target << m_fileDir + '/' + target + ".md"
|
||||||
|
<< m_vaultRoot + '/' + target << m_vaultRoot + '/' + target + ".md";
|
||||||
|
for (const QString &c : cands) {
|
||||||
|
if (QFileInfo::exists(c))
|
||||||
|
return QStringLiteral("<a href=\"%1\">%2</a>")
|
||||||
|
.arg(fileHrefFor(c), esc(target));
|
||||||
|
}
|
||||||
|
return QStringLiteral("<a href=\"%1\" style=\"color:#888\">%2</a>")
|
||||||
|
.arg(wikiHrefFor(target), esc(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString children(cmark_node *n)
|
||||||
|
{
|
||||||
|
QString out;
|
||||||
|
for (cmark_node *c = cmark_node_first_child(n); c; c = cmark_node_next(c))
|
||||||
|
out += inlineAll(c);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString linkNode(cmark_node *n)
|
||||||
|
{
|
||||||
|
const QString url = QString::fromUtf8(cmark_node_get_url(n));
|
||||||
|
const QString label = children(n);
|
||||||
|
const QByteArray scheme = QUrl(url).scheme().toUtf8();
|
||||||
|
|
||||||
|
// Внешние схемы — оставляем напрямую
|
||||||
|
if (scheme == "http" || scheme == "https" || scheme == "mailto"
|
||||||
|
|| scheme == "ftp" || scheme == "flora")
|
||||||
|
return QStringLiteral("<a href=\"%1\">%2</a>").arg(url, label);
|
||||||
|
|
||||||
|
// Локальные пути: файл относительно текущего документа
|
||||||
|
QString resolved;
|
||||||
|
if (QFileInfo(url).isRelative())
|
||||||
|
resolved = QDir(m_fileDir).filePath(url);
|
||||||
|
else
|
||||||
|
resolved = url;
|
||||||
|
|
||||||
|
if (QFileInfo::exists(resolved))
|
||||||
|
return QStringLiteral("<a href=\"%1\">%2</a>")
|
||||||
|
.arg(fileHrefFor(resolved), label);
|
||||||
|
return label; // цель не найдена — просто текст
|
||||||
|
}
|
||||||
|
|
||||||
|
QString imageNode(cmark_node *n)
|
||||||
|
{
|
||||||
|
const QString url = QString::fromUtf8(cmark_node_get_url(n));
|
||||||
|
const QString alt = children(n);
|
||||||
|
QString resolved = QFileInfo(url).isRelative()
|
||||||
|
? QDir(m_fileDir).filePath(url)
|
||||||
|
: url;
|
||||||
|
QString src = imageDataUri(resolved);
|
||||||
|
if (src.isEmpty()) {
|
||||||
|
if (QFileInfo::exists(resolved))
|
||||||
|
src = QStringLiteral("file:%1").arg(QDir::toNativeSeparators(resolved));
|
||||||
|
else
|
||||||
|
src = QStringLiteral("");
|
||||||
|
}
|
||||||
|
return src.isEmpty()
|
||||||
|
? QStringLiteral("<i>[изображение: %1]</i>").arg(esc(alt))
|
||||||
|
: QStringLiteral("<img src=\"%1\" alt=\"%2\"/>").arg(src, esc(alt));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
QString MdParser::render(const QString &markdown, const QString &vaultRoot,
|
||||||
|
const QString &fileDir)
|
||||||
|
{
|
||||||
|
cmark_gfm_core_extensions_ensure_registered();
|
||||||
|
cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT);
|
||||||
|
for (const char *name : {"table", "strikethrough", "autolink", "tasklist"}) {
|
||||||
|
if (cmark_syntax_extension *ext = cmark_find_syntax_extension(name))
|
||||||
|
cmark_parser_attach_syntax_extension(parser, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmark_parser_feed(parser, markdown.toUtf8().constData(),
|
||||||
|
static_cast<size_t>(markdown.toUtf8().size()));
|
||||||
|
cmark_node *doc = cmark_parser_finish(parser);
|
||||||
|
|
||||||
|
Renderer r(m->m_repo, vaultRoot, fileDir);
|
||||||
|
QString html = r.renderTree(doc);
|
||||||
|
|
||||||
|
cmark_node_free(doc);
|
||||||
|
cmark_parser_free(parser);
|
||||||
|
return html;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
// Рендер Markdown (CommonMark + GFM) в HTML для режима просмотра.
|
||||||
|
// Задачи/ссылки/переходы кодируются ссылками schemes:
|
||||||
|
// flora://task/N — переключить задачу на строке N (1-based)
|
||||||
|
// flora://open/<path> — открыть файл хранилища
|
||||||
|
// flora://wiki/<name> — вики-ссылка без найденного файла
|
||||||
|
class MdParser : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MdParser(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
// markdown → HTML. vaultRoot/vaultDir — для разрешения относительных путей.
|
||||||
|
Q_INVOKABLE QString render(const QString &markdown,
|
||||||
|
const QString &vaultRoot,
|
||||||
|
const QString &fileDir);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
Impl *m = nullptr;
|
||||||
|
};
|
||||||
+21
-1
@@ -54,4 +54,24 @@ add_executable(flora_docmgr_test documentmanager_test.cpp
|
|||||||
target_include_directories(flora_docmgr_test PRIVATE ../src)
|
target_include_directories(flora_docmgr_test PRIVATE ../src)
|
||||||
target_link_libraries(flora_docmgr_test PRIVATE
|
target_link_libraries(flora_docmgr_test PRIVATE
|
||||||
Qt6::Core)
|
Qt6::Core)
|
||||||
add_test(NAME flora_docmgr_test COMMAND flora_docmgr_test)
|
add_test(NAME flora_docmgr_test COMMAND flora_docmgr_test)
|
||||||
|
|
||||||
|
# Тест MdParser (рендер Markdown → HTML).
|
||||||
|
add_executable(flora_mdparser_test mdparser_test.cpp
|
||||||
|
../src/markdown/MdParser.cpp
|
||||||
|
../src/markdown/MdParser.h
|
||||||
|
)
|
||||||
|
target_include_directories(flora_mdparser_test PRIVATE ../src)
|
||||||
|
target_compile_definitions(flora_mdparser_test PRIVATE
|
||||||
|
CMARK_GFM_STATIC_DEFINE
|
||||||
|
CMARK_GFM_EXTENSIONS_STATIC_DEFINE)
|
||||||
|
target_link_libraries(flora_mdparser_test PRIVATE
|
||||||
|
Qt6::Core
|
||||||
|
Qt6::Gui
|
||||||
|
cmark::cmark
|
||||||
|
cmark::cmark_extensions
|
||||||
|
)
|
||||||
|
if(TARGET KF6SyntaxHighlighting)
|
||||||
|
target_link_libraries(flora_mdparser_test PRIVATE KF6SyntaxHighlighting)
|
||||||
|
endif()
|
||||||
|
add_test(NAME flora_mdparser_test COMMAND flora_mdparser_test)
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// Тест MdParser (Фаза 6): рендер Markdown → HTML
|
||||||
|
// (заголовки, жирный/курсив/зачёркнутый, таблицы, ссылки, картинки,
|
||||||
|
// задачи с переключаемыми ссылками, подсветка кода, вики-ссылки).
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "markdown/MdParser.h"
|
||||||
|
|
||||||
|
static int fail(const char *msg, const QString &detail = QString())
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "FAIL: %s | %s\n", msg, qPrintable(detail));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
|
QTemporaryDir dir;
|
||||||
|
if (!dir.isValid())
|
||||||
|
return fail("temp dir");
|
||||||
|
|
||||||
|
const QString vault = dir.path();
|
||||||
|
QDir(vault).mkpath("sub");
|
||||||
|
{
|
||||||
|
QFile f(vault + "/sub/b.md");
|
||||||
|
f.open(QIODevice::WriteOnly);
|
||||||
|
f.write("b");
|
||||||
|
f.close();
|
||||||
|
QFile g(vault + "/sub/pic.png");
|
||||||
|
g.open(QIODevice::WriteOnly);
|
||||||
|
g.write("\x89PNG fake");
|
||||||
|
g.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
MdParser p;
|
||||||
|
const QString fileDir = vault + "/sub";
|
||||||
|
|
||||||
|
auto probe = [](const QString &html, const char *sub) {
|
||||||
|
return html.contains(QString::fromUtf8(sub));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Заголовок
|
||||||
|
QString h = p.render("# Заголовок\n", vault, fileDir);
|
||||||
|
if (!probe(h, "<h1>Заголовок</h1>"))
|
||||||
|
return fail("заголовок", h);
|
||||||
|
|
||||||
|
// Жирный / курсив / зачёркнутый
|
||||||
|
QString f = p.render("**ж** *к* ~~з~~\n", vault, fileDir);
|
||||||
|
if (!probe(f, "<b>ж</b>") || !probe(f, "<i>к</i>") || !probe(f, "<s>з</s>"))
|
||||||
|
return fail("инлайн-разметка", f);
|
||||||
|
|
||||||
|
// Таблица GFM
|
||||||
|
QString t = p.render("| A | B |\n|---|---|\n| 1 | 2 |\n", vault, fileDir);
|
||||||
|
if (!probe(t, "<table") || !probe(t, "<th") || !probe(t, "<td"))
|
||||||
|
return fail("таблица", t);
|
||||||
|
|
||||||
|
// Задачи (переключаемые ссылки + галочки)
|
||||||
|
QString sk = p.render("- [ ] задача\n- [x] готова\n", vault, fileDir);
|
||||||
|
if (!probe(sk, "flora://task/") || !probe(sk, "\u2610") || !probe(sk, "\u2611"))
|
||||||
|
return fail("задачи", sk);
|
||||||
|
|
||||||
|
// Относительная ссылка на существующий файл → flora://open
|
||||||
|
QString lk = p.render("[б](b.md)\n", vault, fileDir);
|
||||||
|
if (!probe(lk, "flora://open/") || !probe(lk, ">б</a>"))
|
||||||
|
return fail("ссылка", lk);
|
||||||
|
|
||||||
|
// Вики-ссылка [[b]]
|
||||||
|
QString wk = p.render("[[b]]\n", vault, fileDir);
|
||||||
|
if (!probe(wk, "flora://open/") || !probe(wk, ">b</a>"))
|
||||||
|
return fail("вики-ссылка", wk);
|
||||||
|
|
||||||
|
// Подсветка кода python (должны появиться цветные span'ы)
|
||||||
|
QString c = p.render("```python\nprint(\"hi\")\n```\n", vault, fileDir);
|
||||||
|
if (!probe(c, "language-Python") || !probe(c, "style=\"color:"))
|
||||||
|
return fail("подсветка кода", c);
|
||||||
|
|
||||||
|
// Простой код без языка — не должен падать
|
||||||
|
p.render("```\nпросто\n```\n", vault, fileDir);
|
||||||
|
|
||||||
|
// Язык «С++» из кириллицы должен мапиться на C++ (подсветка сработает)
|
||||||
|
QString cpp = p.render("```С++\nint x = 1;\n```\n", vault, fileDir);
|
||||||
|
if (!probe(cpp, "language-C++"))
|
||||||
|
return fail("алиас С++", cpp);
|
||||||
|
|
||||||
|
// Экранирование HTML
|
||||||
|
QString e = p.render("<script>alert(1)</script>\n", vault, fileDir);
|
||||||
|
if (probe(e, "<script>"))
|
||||||
|
return fail("экранирование", e);
|
||||||
|
|
||||||
|
// Картинка → data: или placeholder (файл невалидный PNG — data не выйдет)
|
||||||
|
QString im = p.render("\n", vault, fileDir);
|
||||||
|
if (!probe(im, "<img") && !probe(im, "изображение"))
|
||||||
|
return fail("картинка", im);
|
||||||
|
|
||||||
|
std::printf("MDPARSER TEST OK\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Vendored
+51
@@ -98,4 +98,55 @@ if(TARGET libgit2package AND NOT TARGET Flora_libgit2_static)
|
|||||||
target_link_libraries(Flora_libgit2_static INTERFACE libgit2package)
|
target_link_libraries(Flora_libgit2_static INTERFACE libgit2package)
|
||||||
target_include_directories(Flora_libgit2_static INTERFACE
|
target_include_directories(Flora_libgit2_static INTERFACE
|
||||||
"${libgit2_SOURCE_DIR}/include")
|
"${libgit2_SOURCE_DIR}/include")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# --- KSyntaxHighlighting (подсветка кода) + Extra CMake Modules ---
|
||||||
|
FetchContent_Declare(extra-cmake-modules
|
||||||
|
GIT_REPOSITORY https://github.com/KDE/extra-cmake-modules.git
|
||||||
|
GIT_TAG v6.19.0
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
set(ENABLE_BSYMBOLICFUNCTIONS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_TESTING OFF) # локально для подпроекта ECM (не трогаем глобальное)
|
||||||
|
FetchContent_MakeAvailable(extra-cmake-modules)
|
||||||
|
unset(BUILD_TESTING)
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${extra-cmake-modules_SOURCE_DIR}/modules")
|
||||||
|
set(ECM_ENABLE_SANITIZERS OFF CACHE BOOL "" FORCE)
|
||||||
|
# ECMConfig из build-дерева ссылается на установку — собираем префикс вручную.
|
||||||
|
set(ECM_PREFIX "${CMAKE_BINARY_DIR}/_ecm_prefix" CACHE PATH "ECM temp prefix" FORCE)
|
||||||
|
file(COPY "${extra-cmake-modules_SOURCE_DIR}/modules" DESTINATION "${ECM_PREFIX}/share/ECM")
|
||||||
|
file(COPY "${extra-cmake-modules_SOURCE_DIR}/find-modules" DESTINATION "${ECM_PREFIX}/share/ECM")
|
||||||
|
if(EXISTS "${extra-cmake-modules_SOURCE_DIR}/kde-modules")
|
||||||
|
file(COPY "${extra-cmake-modules_SOURCE_DIR}/kde-modules" DESTINATION "${ECM_PREFIX}/share/ECM")
|
||||||
|
endif()
|
||||||
|
file(MAKE_DIRECTORY "${ECM_PREFIX}/share/ECM/cmake")
|
||||||
|
file(COPY "${extra-cmake-modules_BINARY_DIR}/ECMConfig.cmake"
|
||||||
|
"${extra-cmake-modules_BINARY_DIR}/ECMConfigVersion.cmake"
|
||||||
|
DESTINATION "${ECM_PREFIX}/share/ECM/cmake")
|
||||||
|
set(ECM_DIR "${ECM_PREFIX}/share/ECM/cmake" CACHE PATH "ECM config dir" FORCE)
|
||||||
|
list(APPEND CMAKE_PREFIX_PATH "${ECM_PREFIX}")
|
||||||
|
|
||||||
|
set(BUILD_TESTING OFF) # локально для подпроекта syntax-highlighting
|
||||||
|
FetchContent_Declare(syntax-highlighting
|
||||||
|
GIT_REPOSITORY https://github.com/KDE/syntax-highlighting.git
|
||||||
|
GIT_TAG v6.19.0
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
set(KSYNTAXHIGHLIGHTING_BUILD_TESTING OFF CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_MakeAvailable(syntax-highlighting)
|
||||||
|
unset(BUILD_TESTING)
|
||||||
|
|
||||||
|
# Индексатор использует std::strong_ordering → требует C++20 (lib остаётся на 17,
|
||||||
|
# чтобы не спотыкаться о qfloat16 в Qt 6.11).
|
||||||
|
if(TARGET katehighlightingindexer)
|
||||||
|
set_target_properties(katehighlightingindexer PROPERTIES
|
||||||
|
CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# КФ6 не добавляет BUILD_INTERFACE include при сборке как подпроектом.
|
||||||
|
if(TARGET KF6SyntaxHighlighting)
|
||||||
|
target_include_directories(KF6SyntaxHighlighting INTERFACE
|
||||||
|
"$<BUILD_INTERFACE:${syntax-highlighting_SOURCE_DIR}/src/lib>"
|
||||||
|
"$<BUILD_INTERFACE:${syntax-highlighting_BINARY_DIR}/src/lib>"
|
||||||
|
"$<BUILD_INTERFACE:${syntax-highlighting_BINARY_DIR}/src/lib/KSyntaxHighlighting>")
|
||||||
endif()
|
endif()
|
||||||
Reference in New Issue
Block a user