Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/common/CSyntaxHighlighter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ void CSyntaxHighlighter::setEnableHighlight(bool isEnable)
m_bHighlight = isEnable;
}

void CSyntaxHighlighter::setInvalidCharHighlight(bool enable)
{
qDebug() << "CSyntaxHighlighter::setInvalidCharHighlight()" << enable;
m_bInvalidCharHighlight = enable;
if (enable) {
setEnableHighlight(true);
}
rehighlight();
}

void CSyntaxHighlighter::highlightBlock(const QString &text)
{
if (!m_bHighlight) {
Expand All @@ -33,4 +43,18 @@ void CSyntaxHighlighter::highlightBlock(const QString &text)

qDebug() << "CSyntaxHighlighter::highlightBlock()";
KSyntaxHighlighting::SyntaxHighlighter::highlightBlock(text);

// 叠加 \00 无效字符高亮:红色背景 + 白色文字
if (m_bInvalidCharHighlight) {
QTextCharFormat fmt;
fmt.setBackground(QColor("#FF0000"));
fmt.setForeground(QColor("#FFFFFF"));
// 匹配字面量 \00(反斜杠 + 两个零)
static const QRegularExpression re("\\\\00");
QRegularExpressionMatchIterator it = re.globalMatch(text);
while (it.hasNext()) {
QRegularExpressionMatch match = it.next();
setFormat(match.capturedStart(), match.capturedLength(), fmt);
}
}
}
5 changes: 5 additions & 0 deletions src/common/CSyntaxHighlighter.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include <KSyntaxHighlighting/Repository>
#include <KSyntaxHighlighting/Definition>
#include <KSyntaxHighlighting/SyntaxHighlighter>
#include <QRegularExpression>
#include <QTextCharFormat>
#include <QColor>

using namespace KSyntaxHighlighting;
class CSyntaxHighlighter : public SyntaxHighlighter
Expand All @@ -15,10 +18,12 @@ class CSyntaxHighlighter : public SyntaxHighlighter
explicit CSyntaxHighlighter(QObject *parent = nullptr);
explicit CSyntaxHighlighter(QTextDocument *pDocument);
void setEnableHighlight(bool isEnable);
void setInvalidCharHighlight(bool enable);

protected:
virtual void highlightBlock(const QString & text) override;

private:
bool m_bHighlight = false;
bool m_bInvalidCharHighlight = false;
};
26 changes: 21 additions & 5 deletions src/common/fileloadthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,19 @@ void FileLoadThread::run()
// 发送文件头信息,用于预先加载数据
QString textEncode = QString::fromLocal8Bit(encode);
if (textEncode.contains("ASCII", Qt::CaseInsensitive) || textEncode.contains("UTF-8", Qt::CaseInsensitive)) {
emit sigPreProcess(encode, indata);
if (indata.contains('\x00')) {
QByteArray headData = indata;
headData.replace('\x00', "\\00");
emit sigPreProcess(encode, headData);
} else {
emit sigPreProcess(encode, indata);
}
} else {
QByteArray outHeadData;
DetectCode::ChangeFileEncodingFormat(indata, outHeadData, textEncode, QString("UTF-8"));
if (outHeadData.contains('\x00')) {
outHeadData.replace('\x00', "\\00");
}
emit sigPreProcess(encode, outHeadData);
}
}
Expand All @@ -75,10 +84,17 @@ void FileLoadThread::run()
qWarning() << "FileLoadThread read error:" << e.what() << "at" << m_strFilePath;

file.close();
emit sigLoadFinished(encode, indata, true);
emit sigLoadFinished(encode, indata, true, false);
return;
}

// NUL 字节检测与转义:将每个 \x00 替换为三个 ASCII 字符 \00
bool hasNul = indata.contains('\x00');
if (hasNul) {
qDebug() << "NUL bytes detected in file, escaping to \\00";
indata.replace('\x00', "\\00");
}

if (!m_encodeHint.isEmpty()) {
qDebug() << "Using encoding hint:" << m_encodeHint;
encode = m_encodeHint;
Expand All @@ -98,17 +114,17 @@ void FileLoadThread::run()
qDebug() << "Final encoding detected:" << textEncode;
if (textEncode.contains("ASCII", Qt::CaseInsensitive) || textEncode.contains("UTF-8", Qt::CaseInsensitive)) {
qDebug() << "Using original encoding, no conversion needed";
emit sigLoadFinished(encode, indata, false);
emit sigLoadFinished(encode, indata, false, hasNul);
} else {
qDebug() << "Converting from" << textEncode << "to UTF-8";
QByteArray outData;
DetectCode::ChangeFileEncodingFormat(indata, outData, textEncode, QString("UTF-8"));
emit sigLoadFinished(encode, outData, false);
emit sigLoadFinished(encode, outData, false, hasNul);
qDebug() << "Encoding conversion completed, output size:" << outData.size();
}
} else {
qWarning() << "Failed to open file:" << m_strFilePath << "error:" << file.errorString();
emit sigLoadFinished("", "", true);
emit sigLoadFinished("", "", true, false);
}

qDebug() << "FileLoadThread finished processing:" << m_strFilePath;
Expand Down
2 changes: 1 addition & 1 deletion src/common/fileloadthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class FileLoadThread : public QThread
signals:
// 预处理信号,优先处理文件头,防止出现加载时间过长的情况
void sigPreProcess(const QByteArray &encode, const QByteArray &content);
void sigLoadFinished(const QByteArray &encode, const QByteArray &content, bool error = false);
void sigLoadFinished(const QByteArray &encode, const QByteArray &content, bool error = false, bool hasNul = false);

private:
QString m_strFilePath;
Expand Down
22 changes: 22 additions & 0 deletions src/controls/warningnotices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ WarningNotices::WarningNotices(MessageType notifyType, QWidget *parent)
setIcon(QIcon(":/images/warning.svg"));
m_reloadBtn = new QPushButton(tr("Reload"), this);
m_saveAsBtn = new QPushButton(qApp->translate("Window", "Save as"), this);
m_editAnywayBtn = new QPushButton(tr("Edit Anyway"), this);
m_reloadBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_saveAsBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_editAnywayBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_editAnywayBtn->setVisible(false);
qDebug() << "Warning buttons initialized";

connect(m_reloadBtn, &QPushButton::clicked, this, &WarningNotices::slotreloadBtnClicked);
connect(m_saveAsBtn, &QPushButton::clicked, this, &WarningNotices::slotsaveAsBtnClicked);
connect(m_editAnywayBtn, &QPushButton::clicked, this, &WarningNotices::slotEditAnywayBtnClicked);
qDebug() << "Warning button signals connected";

#ifdef DTKWIDGET_CLASS_DSizeMode
Expand Down Expand Up @@ -112,3 +116,21 @@ void WarningNotices::slotsaveAsBtnClicked()
emit saveAsBtnClicked();
qDebug() << "slotsaveAsBtnClicked end";
}

void WarningNotices::setEditAnywayBtn()
{
qDebug() << "setEditAnywayBtn";
m_reloadBtn->setVisible(false);
m_saveAsBtn->setVisible(false);
m_editAnywayBtn->setVisible(true);
setWidget(m_editAnywayBtn);
qDebug() << "setEditAnywayBtn end";
}

void WarningNotices::slotEditAnywayBtnClicked()
{
qDebug() << "slotEditAnywayBtnClicked";
this->hide();
emit editAnywayBtnClicked();
qDebug() << "slotEditAnywayBtnClicked end";
}
4 changes: 4 additions & 0 deletions src/controls/warningnotices.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,23 @@ class WarningNotices : public DFloatingMessage
void setReloadBtn();
void setSaveAsBtn();
void clearBtn();
void setEditAnywayBtn();

signals:
void reloadBtnClicked();
void saveAsBtnClicked();
void closeBtnClicked();
void editAnywayBtnClicked();

public slots:
void slotreloadBtnClicked();
void slotsaveAsBtnClicked();
void slotEditAnywayBtnClicked();

private:
QPushButton *m_reloadBtn;
QPushButton *m_saveAsBtn;
QPushButton *m_editAnywayBtn;
QHBoxLayout *m_pLayout;
};

Expand Down
84 changes: 83 additions & 1 deletion src/editor/editwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ EditWrapper::EditWrapper(Window *window, QWidget *parent)
connect(m_pTextEdit, &TextEdit::cursorModeChanged, this, &EditWrapper::handleCursorModeChanged);
connect(m_pWaringNotices, &WarningNotices::reloadBtnClicked, this, &EditWrapper::reloadModifyFile);
connect(m_pWaringNotices, &WarningNotices::saveAsBtnClicked, m_pWindow, &Window::saveAsFile);
connect(m_pWaringNotices, &WarningNotices::editAnywayBtnClicked, this, &EditWrapper::onEditAnyway);
// NOTE: 文本高亮会触发重新布局,与界面布局(拖拽、放大窗口)变更时的布局操作冲突,因此调整更新顺序,在布局后刷新高亮
connect(m_pTextEdit->verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int) {
qDebug() << "EditWrapper connect verticalScrollBar valueChanged";
Expand Down Expand Up @@ -604,6 +605,13 @@ QString EditWrapper::getTextEncode()
bool EditWrapper::saveFile(QByteArray encode)
{
qDebug() << "EditWrapper saveFile, encode:" << encode;

// 预览模式下不允许直接静默保存,交由 Window 层确认弹窗
if (m_bInvalidCharPreview) {
qDebug() << "EditWrapper saveFile, in preview mode, returning false";
return false;
}

QString qstrFilePath = m_pTextEdit->getTruePath();
hideWarningNotices();

Expand Down Expand Up @@ -643,6 +651,54 @@ bool EditWrapper::saveFile(QByteArray encode)
return ok;
}

void EditWrapper::onEditAnyway()
{
qDebug() << "EditWrapper onEditAnyway, allowing edit in preview mode";
m_bInvalidCharEditAllowed = true;
m_pTextEdit->setReadOnly(false);
m_pWaringNotices->hide();
// 保持 m_bInvalidCharPreview = true,直到 Save As 或 Save Anyway 成功
}

void EditWrapper::exitInvalidCharPreview()
{
qDebug() << "EditWrapper exitInvalidCharPreview, exiting preview mode";
m_bInvalidCharPreview = false;
m_bInvalidCharEditAllowed = false;
m_sInvalidCharOriginalPath.clear();
m_pTextEdit->setReadOnly(false);
m_pWaringNotices->hide();
if (m_pSyntaxHighlighter) {
m_pSyntaxHighlighter->setInvalidCharHighlight(false);
}
updateModifyStatus(false);
}

bool EditWrapper::forceSaveInvalidCharFile()
{
qDebug() << "EditWrapper forceSaveInvalidCharFile, saving to original path:" << m_sInvalidCharOriginalPath;
QString savePath = m_sInvalidCharOriginalPath;
TextFileSaver saver(m_pTextEdit->document());
saver.setFilePath(savePath);
saver.setEncoding(m_sCurEncode.toUtf8());
saver.setEndlineFormat(m_pBottomBar->getEndlineFormat() == BottomBar::EndlineFormat::Windows);

bool ok = saver.save();
if (ok) {
qDebug() << "EditWrapper forceSaveInvalidCharFile, save succeeded";
m_sFirstEncode = m_sCurEncode;
QFileInfo fi(savePath);
m_tModifiedDateTime = fi.lastModified();
m_bIsTemFile = false;
m_pTextEdit->setTextEncode(m_sCurEncode);
m_pTextEdit->writeEncodeHistoryRecord();
exitInvalidCharPreview();
} else {
qDebug() << "EditWrapper forceSaveInvalidCharFile, save failed";
}
return ok;
}

void EditWrapper::getPlainTextContent(QByteArray &plainTextContent)
{
qDebug() << "EditWrapper getPlainTextContent";
Expand Down Expand Up @@ -975,7 +1031,7 @@ void EditWrapper::handleFilePreProcess(const QByteArray &encode, const QByteArra
* @param encode 文件编码
* @param content 完整文件内容
*/
void EditWrapper::handleFileLoadFinished(const QByteArray &encode, const QByteArray &content, bool error)
void EditWrapper::handleFileLoadFinished(const QByteArray &encode, const QByteArray &content, bool error, bool hasNul)
{
qDebug() << "File load finished. Encoding:" << encode << "Error:" << error << "Preprocessed:" << m_bHasPreProcess;

Expand Down Expand Up @@ -1017,6 +1073,32 @@ void EditWrapper::handleFileLoadFinished(const QByteArray &encode, const QByteAr
m_pTextEdit->setReadOnly(true);
}

// 无效字符(NUL)预览模式初始化
if (hasNul) {
qDebug() << "EditWrapper handleFileLoadFinished, hasNul is true, entering preview mode";
m_bInvalidCharPreview = true;
m_bInvalidCharEditAllowed = false;
m_sInvalidCharOriginalPath = m_pTextEdit->getTruePath();
m_pTextEdit->setReadOnly(true);
m_pWaringNotices->setMessage(tr("The file contains invalid characters (NUL). Preview mode is read-only."));
m_pWaringNotices->setEditAnywayBtn();
m_pWaringNotices->show();
DMessageManager::instance()->sendMessage(m_pTextEdit, m_pWaringNotices);
// 确保 CSyntaxHighlighter 存在(无语法定义的文件如 .txt 不会在 reinitOnFileLoad 中创建)
if (!m_pSyntaxHighlighter) {
m_pSyntaxHighlighter = new CSyntaxHighlighter(m_pTextEdit->document());
QString themePath = Settings::instance()->settings->option("advance.editor.theme")->value().toString();
if (themePath.contains("dark")) {
m_pSyntaxHighlighter->setTheme(m_Repository.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme));
} else {
m_pSyntaxHighlighter->setTheme(m_Repository.defaultTheme(KSyntaxHighlighting::Repository::LightTheme));
}
}
if (m_pSyntaxHighlighter) {
m_pSyntaxHighlighter->setInvalidCharHighlight(true);
}
}

if (m_bQuit) {
qDebug() << "EditWrapper handleFileLoadFinished, m_bQuit is true, return";
return;
Expand Down
18 changes: 17 additions & 1 deletion src/editor/editwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ class EditWrapper : public QWidget
inline CSyntaxHighlighter *getSyntaxHighlighter() const
{ return m_pSyntaxHighlighter; }

// 无效字符预览模式访问器
inline bool isInvalidCharPreview() const
{ return m_bInvalidCharPreview; }
inline bool isInvalidCharEditAllowed() const
{ return m_bInvalidCharEditAllowed; }
inline QString invalidCharOriginalPath() const
{ return m_sInvalidCharOriginalPath; }
void exitInvalidCharPreview();
bool forceSaveInvalidCharFile();

signals:
void sigClearDoubleCharaterEncode();

Expand All @@ -139,14 +149,15 @@ class EditWrapper : public QWidget
public slots:
// 处理文档预加载数据
void handleFilePreProcess(const QByteArray &encode, const QByteArray &content);
void handleFileLoadFinished(const QByteArray &encode, const QByteArray &content, bool error);
void handleFileLoadFinished(const QByteArray &encode, const QByteArray &content, bool error, bool hasNul = false);
void OnThemeChangeSlot(QString theme);
void UpdateBottomBarWordCnt(int cnt);
void OnUpdateHighlighter();
//set the value of m_bIsTemFile
void setTemFile(bool value);
// 设置恢复光标位置(用于懒加载恢复,避免 O(N²) 扫描)
void setRestoreCursorPosition(int position);
void onEditAnyway();

private:
//第一次打开文件编码
Expand Down Expand Up @@ -183,6 +194,11 @@ public slots:
bool m_bAsyncReadFileFinished = false;
bool m_bHasPreProcess = false; // 预处理标识
int m_nRestoreCursorPosition = -1; // 恢复光标位置提示(-1 表示不指定)

// 无效字符(NUL)预览模式状态
bool m_bInvalidCharPreview = false;
bool m_bInvalidCharEditAllowed = false;
QString m_sInvalidCharOriginalPath;
};

#endif
Loading
Loading