#pragma once #include #include #include #include #include #include class QQmlEngine; class QJSEngine; namespace ZShell::llm { // Syntax highlighting for LLM code blocks via tree-sitter. // // The tree-sitter runtime is linked. Grammar libraries // (libtree-sitter-.so) are dlopen()'d lazily, so a missing // grammar package degrades that language to plain text instead of // breaking the build or the app. Highlight queries are embedded at // build time (highlight-queries/*.scm, vendored from the grammar // repos, MIT). // // The fence language the LLM wrote (```cpp, ```python, ...) is mapped // to a grammar through an alias table. // // highlight() returns a list of span maps: // { "start": int, "length": int, "kind": QString } // where kind is a semantic role (keyword, string, comment, number, // function, type, ...) that QML maps to theme colors. An empty list // means "no highlighting" (unknown language or grammar not installed). class CodeHighlighter : public QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON public: Q_INVOKABLE QVariantList highlight(const QString& code, const QString& language) const; static CodeHighlighter* create(QQmlEngine*, QJSEngine*); struct Grammar { QString lib; // soname to dlopen QString symbol; // tree_sitter_() entry point // Candidate query sources, first wins. Lets typescript reuse // the javascript query when it compiles against the grammar. std::vector queries; // indices into querySources() }; private: struct State { bool bad = false; // permanent failure, do not retry void* lib = nullptr; const void* lang = nullptr; // const TSLanguage* void* query = nullptr; // TSQuery* }; [[nodiscard]] static const QHash& aliases(); [[nodiscard]] const std::vector& querySources() const; // Maps a tree-sitter capture name to a role index (0 = unstyled). [[nodiscard]] static uint8_t roleFor(const char* name, uint32_t length); [[nodiscard]] static const char* roleName(uint8_t role); mutable QHash m_states; static CodeHighlighter* s_instance; }; } // namespace ZShell::llm