修复一些小BUG

This commit is contained in:
JackLee 2025-03-05 19:39:50 +08:00
parent 9fa33257c0
commit b77ab4bb3d
8 changed files with 158 additions and 238 deletions

3
.gitignore vendored
View File

@ -11,7 +11,8 @@ release/*
save/*
.cache/*
.vscode/*
src/ctai_cmark-gfm.cpp
src/ctai_cmark-gfm.h
# Compiled Java class files
*.class

111
ctai_cmark-gfm.cpp Normal file
View File

@ -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);
}
}
}

43
ctai_cmark-gfm.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef CTAI_CMARK_GFM_H
#define CTAI_CMARK_GFM_H
#include <cmark-gfm.h>
#include <QString>
#include <QTextEdit>
#include <memory>
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 927 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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_buffer;
handleBufferOverflow();
// 语法状态检测
SyntaxState newState = detectSyntaxState(m_buffer);
updateSyntaxStack(newState);
// 渲染决策
if (shouldRender(newState))
{
renderContent();
}
}
// 处理缓冲区溢出
void ctai_cmark_gfm::handleBufferOverflow()
{
if (m_buffer.length() > 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<SyntaxTag> 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<QPair<QRegularExpression, SyntaxType>> 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<SyntaxType, QString> 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));
}
}

View File

@ -1,52 +0,0 @@
#ifndef CTAI_CMARK_GFM_H
#define CTAI_CMARK_GFM_H
#include <QTextEdit>
#include <cmark.h>
#include <stack>
#include <QRegularExpression>
#include <QScrollBar>
enum SyntaxType {
CodeBlock, // 代码块
Bold, // 粗体
Italic, // 斜体
Heading, // 标题
List, // 列表
Blockquote, // 引用
Link, // 链接
Image, // 图片
CustomBlock // 自定义块
};
struct SyntaxTag {
SyntaxType type;
int startPos;
QString opener;
QString closer;
};
struct SyntaxState {
QMap<SyntaxType, QString> 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<SyntaxTag> 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

View File

@ -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()