Skip to content
Merged
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
26 changes: 25 additions & 1 deletion src/common/CSyntaxHighlighter.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2017 - 2022 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2017-2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

Expand All @@ -23,11 +23,35 @@
m_bHighlight = isEnable;
}

void CSyntaxHighlighter::setInvalidCharHighlight(bool enable)

Check warning on line 26 in src/common/CSyntaxHighlighter.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

The function 'setInvalidCharHighlight' is never used.
{
qDebug() << "CSyntaxHighlighter::setInvalidCharHighlight()" << enable;
m_bInvalidCharHighlight = enable;
if (enable) {
setEnableHighlight(true);
}
rehighlight();
}

void CSyntaxHighlighter::highlightBlock(const QString &text)
{
if (!m_bHighlight) {
return;
}

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);
}
}
}
7 changes: 6 additions & 1 deletion src/common/CSyntaxHighlighter.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// SPDX-FileCopyrightText: 2017 - 2022 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2017-2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once
#include <KSyntaxHighlighting/Repository>

Check warning on line 6 in src/common/CSyntaxHighlighter.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <KSyntaxHighlighting/Repository> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <KSyntaxHighlighting/Definition>

Check warning on line 7 in src/common/CSyntaxHighlighter.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <KSyntaxHighlighting/Definition> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <KSyntaxHighlighting/SyntaxHighlighter>

Check warning on line 8 in src/common/CSyntaxHighlighter.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <KSyntaxHighlighting/SyntaxHighlighter> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QRegularExpression>

Check warning on line 9 in src/common/CSyntaxHighlighter.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QRegularExpression> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QTextCharFormat>

Check warning on line 10 in src/common/CSyntaxHighlighter.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QTextCharFormat> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QColor>

Check warning on line 11 in src/common/CSyntaxHighlighter.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QColor> not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace KSyntaxHighlighting;
class CSyntaxHighlighter : public SyntaxHighlighter
Expand All @@ -15,10 +18,12 @@
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;
};
27 changes: 21 additions & 6 deletions src/common/fileloadthread.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2017 - 2022 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2017-2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

Expand Down Expand Up @@ -36,11 +36,20 @@ 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 (textEncode.contains("ASCII", Qt::CaseInsensitive) || textEncode.contains("UTF-8", Qt::CaseInsensitive)) {
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 @@ -54,10 +63,16 @@ void FileLoadThread::run()
qWarning() << Q_FUNC_INFO << "Read file data error, " << QString(e.what());

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) {
indata.replace('\x00', "\\00");
}

if (encode.isEmpty()) {
//编码识别,如果文件数据大于1M,则只裁剪出1M文件数据去做编码探测
QByteArray dateUsedForCodeIdentify;
Expand All @@ -71,11 +86,11 @@ void FileLoadThread::run()

QString textEncode = QString::fromLocal8Bit(encode);
if (textEncode.contains("ASCII", Qt::CaseInsensitive) || textEncode.contains("UTF-8", Qt::CaseInsensitive)) {
emit sigLoadFinished(encode, indata, false);
emit sigLoadFinished(encode, indata, false, hasNul);
} else {
QByteArray outData;
DetectCode::ChangeFileEncodingFormat(indata, outData, textEncode, QString("UTF-8"));
emit sigLoadFinished(encode, outData, false);
emit sigLoadFinished(encode, outData, false, hasNul);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/common/fileloadthread.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2017 - 2022 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2017-2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

Expand All @@ -19,7 +19,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
20 changes: 19 additions & 1 deletion src/controls/warningnotices.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019 - 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2019-2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

Expand All @@ -22,11 +22,15 @@ 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);

connect(m_reloadBtn, &QPushButton::clicked, this, &WarningNotices::slotreloadBtnClicked);
connect(m_saveAsBtn, &QPushButton::clicked, this, &WarningNotices::slotsaveAsBtnClicked);
connect(m_editAnywayBtn, &QPushButton::clicked, this, &WarningNotices::slotEditAnywayBtnClicked);

#ifdef DTKWIDGET_CLASS_DSizeMode
DDialogCloseButton *closeBtn = findChild<DDialogCloseButton *>();
Expand Down Expand Up @@ -83,3 +87,17 @@ void WarningNotices::slotsaveAsBtnClicked()
this->hide();
emit saveAsBtnClicked();
}

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

void WarningNotices::slotEditAnywayBtnClicked()
{
this->hide();
emit editAnywayBtnClicked();
}
6 changes: 5 additions & 1 deletion src/controls/warningnotices.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019 - 2022 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2019-2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

Expand All @@ -23,19 +23,23 @@
void setReloadBtn();
void setSaveAsBtn();
void clearBtn();
void setEditAnywayBtn();

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

public slots:

Check warning on line 34 in src/controls/warningnotices.h

View workflow job for this annotation

GitHub Actions / cppcheck

There is an unknown macro here somewhere. Configuration is required. If slots is a macro then please configure it.
void slotreloadBtnClicked();
void slotsaveAsBtnClicked();
void slotEditAnywayBtnClicked();

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

Expand Down
81 changes: 79 additions & 2 deletions src/editor/editwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,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) {
OnUpdateHighlighter();
Expand Down Expand Up @@ -507,6 +508,11 @@ QString EditWrapper::getTextEncode()
*/
bool EditWrapper::saveFile(QByteArray encode)
{
// 预览模式下不允许直接静默保存,交由 Window 层确认弹窗
if (m_bInvalidCharPreview) {
return false;
}

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

Expand Down Expand Up @@ -541,6 +547,47 @@ bool EditWrapper::saveFile(QByteArray encode)
return ok;
}

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

void EditWrapper::exitInvalidCharPreview()
{
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()
{
QString savePath = m_sInvalidCharOriginalPath;
TextFileSaver saver(m_pTextEdit->document());
if (BottomBar::EndlineFormat::Windows == m_pBottomBar->getEndlineFormat())
saver.setWindowsEndlineFormat(true);
saver.setFilePath(savePath);
saver.setEncoding(m_sCurEncode.toUtf8());

bool ok = saver.save();
if (ok) {
m_sFirstEncode = m_sCurEncode;
QFileInfo fi(savePath);
m_tModifiedDateTime = fi.lastModified();
m_bIsTemFile = false;
exitInvalidCharPreview();
}
return ok;
}

void EditWrapper::getPlainTextContent(QByteArray &plainTextContent)
{
QString strPlainText = m_pTextEdit->toPlainText();
Expand Down Expand Up @@ -823,7 +870,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)
{
// 判断是否预加载,若已预加载,则无需重新初始化
if (!m_bHasPreProcess) {
Expand Down Expand Up @@ -857,6 +904,31 @@ void EditWrapper::handleFileLoadFinished(const QByteArray &encode, const QByteAr
m_pTextEdit->setReadOnly(true);
}

// 无效字符(NUL)预览模式初始化
if (hasNul) {
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) {
return;
}
Expand Down Expand Up @@ -1023,7 +1095,12 @@ void EditWrapper::OnUpdateHighlighter()
auto rehighlightBlock = [this](const QTextBlock &block) {
m_pSyntaxHighlighter->setEnableHighlight(true);
m_pSyntaxHighlighter->rehighlightBlock(block);
m_pSyntaxHighlighter->setEnableHighlight(false);
// NUL 预览模式下保持 m_bHighlight=true,使 loadContent 的 insertText 触发的
// 延迟重排(contentsChange → _q_reformatBlocks)能正常重新应用 \00 高亮,
// 首屏立即可见无需滚动(与 master 行为一致);普通文件仍重置 false 保留性能优化。
if (!m_bInvalidCharPreview) {
m_pSyntaxHighlighter->setEnableHighlight(false);
}
};

if (foundBlock.isValid() && foundBlock < beginBlock) {
Expand Down
20 changes: 18 additions & 2 deletions src/editor/editwrapper.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2017 - 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2017-2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

Expand Down 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,12 +149,13 @@ 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);
void onEditAnyway();

private:
//第一次打开文件编码
Expand Down Expand Up @@ -180,6 +191,11 @@ public slots:

bool m_bAsyncReadFileFinished = false;
bool m_bHasPreProcess = false; // 预处理标识

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

#endif
Loading
Loading