修复一些小BUG
This commit is contained in:
parent
9fa33257c0
commit
b77ab4bb3d
3
.gitignore
vendored
3
.gitignore
vendored
@ -11,7 +11,8 @@ release/*
|
|||||||
save/*
|
save/*
|
||||||
.cache/*
|
.cache/*
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
src/ctai_cmark-gfm.cpp
|
||||||
|
src/ctai_cmark-gfm.h
|
||||||
# Compiled Java class files
|
# Compiled Java class files
|
||||||
*.class
|
*.class
|
||||||
|
|
||||||
|
|||||||
111
ctai_cmark-gfm.cpp
Normal file
111
ctai_cmark-gfm.cpp
Normal 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
43
ctai_cmark-gfm.h
Normal 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 |
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
@ -53,7 +53,7 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
|
|||||||
m_msg_menu->setObjectName("m_msg_menu");
|
m_msg_menu->setObjectName("m_msg_menu");
|
||||||
m_msg_fold->setObjectName("m_msg_fold");
|
m_msg_fold->setObjectName("m_msg_fold");
|
||||||
m_msg_fold->setIcon(QIcon(":res/img/btn/btn_info_up.png"));
|
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->addItem(sparcer_item);
|
||||||
header_opts_Layout->addWidget(m_msg_copy);
|
header_opts_Layout->addWidget(m_msg_copy);
|
||||||
header_opts_Layout->addWidget(m_msg_save);
|
header_opts_Layout->addWidget(m_msg_save);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user