diff --git a/.gitignore b/.gitignore index e308aa5..c9be50e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,8 @@ release/* save/* .cache/* .vscode/* - +src/ctai_cmark-gfm.cpp +src/ctai_cmark-gfm.h # Compiled Java class files *.class diff --git a/ctai_cmark-gfm.cpp b/ctai_cmark-gfm.cpp new file mode 100644 index 0000000..90dce5f --- /dev/null +++ b/ctai_cmark-gfm.cpp @@ -0,0 +1,111 @@ +#include "ctai_cmark-gfm.h" + +namespace ctai_cmark_gfm { + +MarkdownStreamProcessor::MarkdownStreamProcessor(QTextEdit* textEdit) + : m_textEdit(textEdit) + , m_buffer() + , m_tempText() { +} + +MarkdownStreamProcessor::~MarkdownStreamProcessor() { + // 确保在销毁时处理所有剩余的缓冲内容 + if (!m_buffer.isEmpty()) { + handleBufferOverflow(); + } +} + +void MarkdownStreamProcessor::appendMarkdown(const QString& text) { + // 将新内容添加到缓冲区 + QString newBuffer = m_buffer + text; + + // 检查是否超出缓冲区大小 + if (newBuffer.size() > BUFFER_SIZE) { + handleBufferOverflow(); + newBuffer = text; + } + + // 检查是否只包含开始标记 + if (hasMarkdownStart(text) && !hasMarkdownEnd(text)) { + m_buffer = newBuffer; + m_tempText = text; + m_textEdit->append(text); + return; + } + + // 检查是否包含结束标记 + if (hasMarkdownEnd(text)) { + QString fullContent = m_buffer + text; + QString rendered = renderMarkdown(fullContent); + replaceTempText(rendered); + m_buffer.clear(); + return; + } + + m_buffer = newBuffer; +} + +bool MarkdownStreamProcessor::hasMarkdownStart(const QString& text) { + // 检查常见的Markdown开始标记 + static const QStringList startMarkers = { + "```", "~~~", "#", ">", "*", "_", "-", "+", "[", "|" + }; + + for (const QString& marker : startMarkers) { + if (text.contains(marker)) { + return true; + } + } + return false; +} + +bool MarkdownStreamProcessor::hasMarkdownEnd(const QString& text) { + // 检查常见的Markdown结束标记 + static const QStringList endMarkers = { + "```", "~~~", "\n\n", "]", "*", "_" + }; + + for (const QString& marker : endMarkers) { + if (text.contains(marker)) { + return true; + } + } + return false; +} + +QString MarkdownStreamProcessor::renderMarkdown(const QString& text) { + QByteArray textUtf8 = text.toUtf8(); + char* rendered = cmark_markdown_to_html( + textUtf8.constData(), + textUtf8.size(), + CMARK_OPT_DEFAULT + ); + + QString result = QString::fromUtf8(rendered); + free(rendered); + return result; +} + +void MarkdownStreamProcessor::handleBufferOverflow() { + if (!m_buffer.isEmpty()) { + QString rendered = renderMarkdown(m_buffer); + replaceTempText(rendered); + m_buffer.clear(); + } +} + +void MarkdownStreamProcessor::replaceTempText(const QString& newContent) { + if (!m_tempText.isEmpty()) { + // 获取当前文本 + QString currentText = m_textEdit->toPlainText(); + // 替换临时文本 + currentText.replace(m_tempText, newContent); + m_textEdit->setHtml(currentText); + m_tempText.clear(); + } else { + m_textEdit->append(newContent); + } +} + +} + diff --git a/ctai_cmark-gfm.h b/ctai_cmark-gfm.h new file mode 100644 index 0000000..de91149 --- /dev/null +++ b/ctai_cmark-gfm.h @@ -0,0 +1,43 @@ +#ifndef CTAI_CMARK_GFM_H +#define CTAI_CMARK_GFM_H + +#include +#include +#include +#include + +namespace ctai_cmark_gfm { + +class MarkdownStreamProcessor { +public: + explicit MarkdownStreamProcessor(QTextEdit* textEdit); + ~MarkdownStreamProcessor(); + + // 追加内容并处理Markdown + void appendMarkdown(const QString& text); + +private: + static constexpr size_t BUFFER_SIZE = 4096; + + QTextEdit* m_textEdit; + QString m_buffer; + QString m_tempText; + + // 检查是否包含Markdown开始标记 + bool hasMarkdownStart(const QString& text); + + // 检查是否包含Markdown结束标记 + bool hasMarkdownEnd(const QString& text); + + // 渲染Markdown内容 + QString renderMarkdown(const QString& text); + + // 处理缓冲区溢出 + void handleBufferOverflow(); + + // 替换临时文本 + void replaceTempText(const QString& newContent); +}; + +} +#endif \ No newline at end of file diff --git a/res/img/btn/btn_info_down.png b/res/img/btn/btn_info_down.png index 5f2bd85..3166d61 100644 Binary files a/res/img/btn/btn_info_down.png and b/res/img/btn/btn_info_down.png differ diff --git a/res/img/btn/btn_info_up.png b/res/img/btn/btn_info_up.png index 58d3ad0..0918bd1 100644 Binary files a/res/img/btn/btn_info_up.png and b/res/img/btn/btn_info_up.png differ diff --git a/src/ctai_cmark-gfm.cpp b/src/ctai_cmark-gfm.cpp deleted file mode 100644 index 06e408a..0000000 --- a/src/ctai_cmark-gfm.cpp +++ /dev/null @@ -1,183 +0,0 @@ -#include "ctai_cmark-gfm.h" - -ctai_cmark_gfm::ctai_cmark_gfm(QTextEdit *editor, QObject *parent) : QObject(parent), - m_editor(editor), - m_bufferMax(8192) -{ -} - -void ctai_cmark_gfm::processInput(const QString &text) -{ - // 更新缓冲区 - m_buffer += text; - qDebug()<<"处理的块:"< m_bufferMax * 1.2) - { - int removeCount = m_buffer.length() - m_bufferMax; - m_buffer.remove(0, removeCount); - adjustSyntaxPositions(removeCount); - } -} -// 调整语法标签位置 -void ctai_cmark_gfm::adjustSyntaxPositions(int offset) -{ - std::stack adjustedStack; - while (!m_syntaxStack.empty()) - { - SyntaxTag tag = m_syntaxStack.top(); - m_syntaxStack.pop(); - tag.startPos = qMax(0, tag.startPos - offset); - if (tag.startPos >= 0) - { - adjustedStack.push(tag); - } - } - m_syntaxStack = adjustedStack; -} -// 语法状态检测 -SyntaxState ctai_cmark_gfm::detectSyntaxState(const QString &buffer) -{ - SyntaxState state; - static const QVector> syntaxPatterns = { - {QRegularExpression(R"(\*\*(?!\s))"), Bold}, - {QRegularExpression(R"(\*(?!\s))"), Italic}, - {QRegularExpression(R"(^#{1,6}\s)"), Heading}, - {QRegularExpression(R"(^```+[\s\S]*?```)", QRegularExpression::MultilineOption), CodeBlock}, - {QRegularExpression(R"(^[\*\+-]\s)"), List}, - {QRegularExpression(R"(^>\s)"), Blockquote}, - {QRegularExpression(R"($$.*?$$$.*?$)"), Link}, - {QRegularExpression(R"(!$$.*?$$$.*?$)"), Image}}; - - for (const auto &[pattern, type] : syntaxPatterns) - { - QRegularExpressionMatchIterator it = pattern.globalMatch(buffer); - while (it.hasNext()) - { - QRegularExpressionMatch match = it.next(); - state.activeTags.insert(type, match.captured()); - } - } - - state.needsClosure = !m_syntaxStack.empty() && - (buffer.length() - m_syntaxStack.top().startPos) > m_bufferMax / 2; - return state; -} -// 更新语法标签栈 -void ctai_cmark_gfm::updateSyntaxStack(const SyntaxState &newState) -{ - // 处理标签闭合 - auto it = newState.activeTags.begin(); - while (it != newState.activeTags.end()) - { - if (it.key() == CodeBlock && m_syntaxStack.top().type == CodeBlock) - { - m_syntaxStack.pop(); - } - // 其他标签闭合逻辑... - ++it; - } - - // 处理新标签 - for (auto type : newState.activeTags.keys()) - { - SyntaxTag newTag; - newTag.type = type; - newTag.startPos = m_buffer.lastIndexOf(newState.activeTags[type]); - newTag.opener = newState.activeTags[type]; - newTag.closer = getCloserForType(type); - m_syntaxStack.push(newTag); - } -} -// 获取闭合标签 -QString ctai_cmark_gfm::getCloserForType(SyntaxType type) -{ - static const QMap closers = { - {CodeBlock, "\n```"}, - {Bold, "**"}, - {Italic, "*"}, - {Heading, "\n"}, - {List, "\n"}, - {Blockquote, "\n"}, - {Link, ")"}, - {Image, ")"}}; - return closers.value(type, ""); -} - -// 渲染决策 -bool ctai_cmark_gfm::shouldRender(const SyntaxState &state) -{ - return state.needsClosure || - (!m_syntaxStack.empty() && m_buffer.length() - m_syntaxStack.top().startPos > 512) || - m_buffer.length() >= m_bufferMax; -} - -void ctai_cmark_gfm::renderContent() -{ - QString renderContent = m_buffer; - - // 自动闭合未完成的标签 - while (!m_syntaxStack.empty()) - { - const SyntaxTag &tag = m_syntaxStack.top(); - if (!renderContent.contains(tag.closer)) - { - renderContent += tag.closer; - } - m_syntaxStack.pop(); - } - - // 使用cmark解析 - cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT); - cmark_parser_feed(parser, renderContent.toUtf8().constData(), renderContent.size()); - cmark_node *doc = cmark_parser_finish(parser); - - // 转换为HTML - char *html = cmark_render_html(doc, CMARK_OPT_DEFAULT); - updateEditor(QString::fromUtf8(html)); - - // 清理资源 - cmark_node_free(doc); - cmark_parser_free(parser); - free(html); - - // 重置缓冲区 - m_buffer.clear(); -} -// 更新编辑器内容(保持滚动位置) -void ctai_cmark_gfm::updateEditor(const QString &html) -{ - QTextCursor cursor(m_editor->document()); - int oldHeight = m_editor->document()->size().height(); - bool atBottom = m_editor->verticalScrollBar()->value() == - m_editor->verticalScrollBar()->maximum(); - - cursor.select(QTextCursor::Document); - cursor.insertHtml(html); - - // 滚动位置处理 - if (atBottom) - { - m_editor->verticalScrollBar()->setValue(m_editor->verticalScrollBar()->maximum()); - } - else - { - int newHeight = m_editor->document()->size().height(); - m_editor->verticalScrollBar()->setValue(m_editor->verticalScrollBar()->value() + (newHeight - oldHeight)); - } -} \ No newline at end of file diff --git a/src/ctai_cmark-gfm.h b/src/ctai_cmark-gfm.h deleted file mode 100644 index 37f486b..0000000 --- a/src/ctai_cmark-gfm.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef CTAI_CMARK_GFM_H -#define CTAI_CMARK_GFM_H - -#include -#include -#include -#include -#include -enum SyntaxType { - CodeBlock, // 代码块 - Bold, // 粗体 - Italic, // 斜体 - Heading, // 标题 - List, // 列表 - Blockquote, // 引用 - Link, // 链接 - Image, // 图片 - CustomBlock // 自定义块 -}; -struct SyntaxTag { - SyntaxType type; - int startPos; - QString opener; - QString closer; -}; - -struct SyntaxState { - QMap activeTags; - bool needsClosure = false; -}; - -class ctai_cmark_gfm : public QObject { - Q_OBJECT -public: - ctai_cmark_gfm(QTextEdit *editor, QObject *parent = nullptr); - void processInput(const QString &text); -private: - QTextEdit *m_editor; - const int m_bufferMax; - QString m_buffer; - std::stack m_syntaxStack; // 语法标签栈 - void handleBufferOverflow(); - void adjustSyntaxPositions(int offset); - SyntaxState detectSyntaxState(const QString &buffer); - void updateSyntaxStack(const SyntaxState &newState); - bool shouldRender(const SyntaxState &state); - QString getCloserForType(SyntaxType type); - void renderContent(); - void updateEditor(const QString &html); -}; - -#endif \ No newline at end of file diff --git a/src/ctai_history_textedit.cpp b/src/ctai_history_textedit.cpp index 0ae191c..7faee6a 100644 --- a/src/ctai_history_textedit.cpp +++ b/src/ctai_history_textedit.cpp @@ -53,7 +53,7 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode) m_msg_menu->setObjectName("m_msg_menu"); m_msg_fold->setObjectName("m_msg_fold"); m_msg_fold->setIcon(QIcon(":res/img/btn/btn_info_up.png")); - m_msg_fold->setMinimumSize(QSize(25, 25)); + m_msg_fold->setIconSize(QSize(25, 25)); header_opts_Layout->addItem(sparcer_item); header_opts_Layout->addWidget(m_msg_copy); header_opts_Layout->addWidget(m_msg_save); @@ -308,7 +308,7 @@ void ctai_history_textedit::add_header_message(const model_data &message) void ctai_history_textedit::on_delete_clicked() { // 发送删除请求信号 - emit delete_requested(m_msg_sned_id); + emit delete_requested(m_msg_sned_id); } void ctai_history_textedit::on_copy_clicked()