修复一些小BUG
This commit is contained in:
parent
9fa33257c0
commit
b77ab4bb3d
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
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_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()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user