1 Star 0 Fork 1.8K

莫干山/ndd

forked from 爬山虎/ndd 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
Resources
cceditor
installer
macicon
macpro
notepad
qscint
qss
release
1.png
2.png
CmpareMode.cpp
CmpareMode.h
Encode.cpp
Encode.h
LICENSE
MediatorDisplay.cpp
MediatorDisplay.h
MediatorFileTree.cpp
MediatorFileTree.h
QTreeWidgetSortItem.cpp
QTreeWidgetSortItem.h
README.md
RcTreeWidget.cpp
RcTreeWidget.h
RealCompare.pri
RealCompare.pro
RealCompare.qrc
RealCompare.rc
StrategyCompare.h
changelog.txt
closeDlg.cpp
closeDlg.h
closeDlg.ui
columnedit.cpp
columnedit.h
columnedit.ui
command.cpp
command.h
common.cpp
common.h
ctipwin.cpp
ctipwin.h
ctipwin.ui
diff.h
dirfindfile.cpp
dirfindfile.h
dirfindfile.ui
doctypelistview.cpp
doctypelistview.h
doctypelistview.ui
donate.cpp
donate.h
donate.ui
draglineedit.cpp
draglineedit.h
encodeconvert.cpp
encodeconvert.h
encodeconvert.ui
filecmprulewin.cpp
filecmprulewin.h
filecmprulewin.ui
findcmpwin.cpp
findcmpwin.h
findcmpwin.ui
findresultwin.cpp
findresultwin.h
findresultwin.ui
findwin.cpp
findwin.h
findwin.ui
gotolinewin.cpp
gotolinewin.h
gotolinewin.ui
hexcmprangewin.cpp
hexcmprangewin.h
hexcmprangewin.ui
hexfilegoto.cpp
hexfilegoto.h
hexfilegoto.ui
hexrulewin.cpp
hexrulewin.h
hexrulewin.ui
jsondeploy.cpp
jsondeploy.h
mac.icns
main.cpp
mystyle.qss
mytreeview.cpp
mytreeview.h
ndstyleditemdelegate.cpp
ndstyleditemdelegate.h
optionsview.cpp
optionsview.h
optionsview.ui
progresswin.cpp
progresswin.h
progresswin.ui
qscidisplaywindow.cpp
qscidisplaywindow.h
qtlangset.cpp
qtlangset.h
qtlangset.ui
rcglobal.cpp
rcglobal.h
realcompare_zh.qm
realcompare_zh.ts
renamewin.cpp
renamewin.h
renamewin.ui
replacecommand.h
resource.h
rgba_icons.h
scintillaeditview.cpp
scintillaeditview.h
scintillahexeditview.cpp
scintillahexeditview.h
statuswidget.cpp
statuswidget.h
statuswidget.ui
styleset.cpp
styleset.h
texteditsetwin.cpp
texteditsetwin.h
texteditsetwin.ui
textfind.cpp
textfind.h
textfind.ui
克隆/下载
encodeconvert.cpp 17.97 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
#include "encodeconvert.h"
#include "rcglobal.h"
#include "CmpareMode.h"
#include "doctypelistview.h"
#include <QFileDialog>
#include <QTreeWidgetItem>
#include <QDateTime>
#include <QFutureWatcher>
#include <QString>
#include <QtConcurrent>
#include <QInputDialog>
#include <QDragEnterEvent>
const int ITEM_CODE = Qt::UserRole + 1;
static QString fileSuffix(const QString& filePath)
{
QFileInfo fi(filePath);
return fi.suffix();
}
static QString getFileSizeFormat(qint64 size)
{
#if 0
if (size <= 1000)
{
return QString("%1").arg(size);
}
QString fileSize = QString("%1").arg(size);
return QString("%1,%2").arg(fileSize.left(fileSize.count() - 3)).arg(fileSize.right(3));
#endif
return QString::number(size);
}
EncodeConvert::EncodeConvert(QWidget *parent): QWidget(parent), m_commitCmpFileNums(0), m_finishCmpFileNums(0), m_menu(nullptr)
{
ui.setupUi(this);
m_extComBoxNum = 0;
connect(ui.treeWidget, &QTreeWidget::itemPressed, this, &EncodeConvert::slot_itemClicked);
setAcceptDrops(true);
}
EncodeConvert::~EncodeConvert()
{
for (auto var : m_supportFileExt)
{
delete var;
}
m_supportFileExt.clear();
}
bool EncodeConvert::isSupportExt(int index, QString ext)
{
bool ret = false;
if (0 == index)
{
ret = DocTypeListView::isSupportExt(ext);
}
else if (index >= 1)
{
int i = index - 1;
if (i < m_supportFileExt.count())
{
ret = m_supportFileExt[i]->contains(ext);
}
}
return ret;
}
//右键菜单
void EncodeConvert::slot_itemClicked(QTreeWidgetItem* item, int /*column*/)
{
if ((item != nullptr) && (Qt::RightButton == QGuiApplication::mouseButtons()))
{
if (m_menu == nullptr)
{
m_menu = new QMenu(this);
m_menu->addAction(tr("&Show File in Explorer..."), this, [&]() {
QString path, cmd;
QTreeWidgetItem* it = ui.treeWidget->currentItem();
if (it == nullptr)
{
return;
}
path = QString("%1").arg(it->data(0, Qt::ToolTipRole).toString());
#ifdef _WIN32
path = path.replace("/", "\\");
cmd = QString("explorer.exe /select,%1").arg(path);
#else
path = path.replace("\\", "/");
cmd = QString("open -R %1").arg(path);
#endif
QProcess process;
process.startDetached(cmd);
});
}
m_menu->move(QCursor::pos());
m_menu->show();
}
}
//用户自定义类型
void EncodeConvert::slot_userDefineExt()
{
bool ok = false;
QString text = QInputDialog::getText(this, tr("input file ext()"),tr("ext (Split With :)"), QLineEdit::Normal, QString(".h:.cpp"), &ok);
if (ok && !text.isEmpty())
{
text = text.trimmed();
ui.extComboBox->addItem(text);
QStringList extList = text.split(":");
QMap<QString, bool>* p = new QMap<QString, bool>;
for (QString var : extList)
{
if (var.startsWith("."))
{
p->insert(var.mid(1), true);
}
}
m_supportFileExt.append(p);
++m_extComBoxNum;
ui.extComboBox->setCurrentIndex(m_extComBoxNum);
}
}
//打开文件目录
void EncodeConvert::slot_selectFile()
{
//加载左边的文件树
QString rootpath = QFileDialog::getExistingDirectory(this, tr("Open Directory"), QString(), QFileDialog::DontResolveSymlinks);
if (!rootpath.isEmpty())
{
ui.treeWidget->clear();
m_fileAttris.clear();
loadDir(rootpath);
setItemIntervalBackground();
scanFileCode();
}
}
int EncodeConvert::allfile(QTreeWidgetItem* root_, QString path_)
{
QList<WalkFileInfo> dirsList;
WalkFileInfo oneDir(0, root_, path_);
dirsList.append(oneDir);
int fileNums = 0;
m_fileDirPath = path_;
while (!dirsList.isEmpty())
{
WalkFileInfo curDir = dirsList.first();
dirsList.pop_front();
QTreeWidgetItem* root = curDir.root;
QString path = curDir.path;
int direction = curDir.direction;
/*添加path路径文件*/
QDir dir(path); //遍历各级子目录
//先获取文件到列表
//再获取文件夹到列表
QFileInfoList folder_list = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); //获取当前所有目录
for (int i = 0; i != folder_list.size(); ++i) //自动递归添加各目录到上一级目录
{
QString namepath = folder_list.at(i).absoluteFilePath(); //获取路径
QFileInfo folderinfo = folder_list.at(i);
QString name = folderinfo.fileName(); //获取目录名
QTreeWidgetItem* childroot = new QTreeWidgetItem(QStringList() << name);
childroot->setIcon(0, QIcon(":/Resources/img/dir.png"));
root->addChild(childroot); //将当前目录添加成path的子项
fileAttriNode node;
node.type = RC_DIR;//是目录
node.selfItem = childroot;
node.parent = root;
node.relativePath = folderinfo.absoluteFilePath();
//把路径名称保存到tips中,后续需要这个来排序,下同
childroot->setData(0, Qt::ToolTipRole, node.relativePath);
m_fileAttris.append(node);
WalkFileInfo oneDir(direction, childroot, namepath);
dirsList.push_front(oneDir);
}
QDir dir_file(path);
dir_file.setFilter(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks);//获取当前所有文件
QFileInfoList list_file = dir_file.entryInfoList();
for (int i = 0; i < list_file.size(); ++i)
{ //将当前目录中所有文件添加到treewidget中
QFileInfo fileInfo = list_file.at(i);
QString name2 = fileInfo.fileName();
QTreeWidgetItem* child = new QTreeWidgetItem(QStringList() << name2);
child->setIcon(0, QIcon(":/Resources/img/point.png"));
child->setText(1, getFileSizeFormat(fileInfo.size()));
/*QString lastModifyTime = fileInfo.lastModified().toString("yy/MM/dd hh:mm:ss");
child->setText(2, lastModifyTime);*/
root->addChild(child);
fileAttriNode node;
node.type = RC_FILE;//是文件
node.selfItem = child;
node.parent = root;
node.relativePath = fileInfo.absoluteFilePath();
//把路径名称保存到tips中,后续需要这个来排序,下同
child->setData(0, Qt::ToolTipRole, node.relativePath);
m_fileAttris.append(node);
}
fileNums += list_file.size();
}
return fileNums;
}
int EncodeConvert::loadDir(QString rootDirPath)
{
QString rootpath = rootDirPath;
QTreeWidgetItem* root = nullptr;
int fileNums = 0;
ui.treeWidget->setColumnWidth(0, 400);
ui.treeWidget->clear();
root = new QTreeWidgetItem(ui.treeWidget);
root->setText(0, rootpath);
root->setExpanded(true);
//第一个节点是目录根节点
fileAttriNode node;
node.type = RC_DIR;//是目录
node.selfItem = root;
node.parent = nullptr;
node.relativePath = ".";
m_fileAttris.append(node);
fileNums = allfile(root, rootpath);
return fileNums;
}
QFuture<EncodeThreadParameter*> EncodeConvert::commitTask(std::function<EncodeThreadParameter* (EncodeThreadParameter*)> fun, EncodeThreadParameter* parameter)
{
/* 这里最开始准备使用信号提交多线程,但是发现std:;function无法使用槽函数机制,需要自己是实现元对象
* 直接使用QtConcurrent::run机制,不仅简单许多,而且在网上看了资料
*/
return QtConcurrent::run(fun, parameter);
}
//对比左右文件的大小,sha1值来判断文件是否相等
QFuture<EncodeThreadParameter_*> EncodeConvert::checkFileCode(QString filePath, QTreeWidgetItem* item)
{
EncodeThreadParameter_* p = new EncodeThreadParameter_(filePath);
p->item = item;
//int 0相等 1 不等
return commitTask([](EncodeThreadParameter_* parameter)->EncodeThreadParameter_*
{
parameter->code = CmpareMode::scanFileRealCode(parameter->filepath);
return parameter;
}
, p);
}
CODE_ID EncodeConvert::convertFileToCode(QString& filePath, CODE_ID srcCode, CODE_ID dstCode)
{
if (srcCode == CODE_ID::UNKOWN)
{
return CODE_ID::UNKOWN;
}
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::ExistingOnly))
{
return CODE_ID::UNKOWN;
}
QByteArray content = file.readAll();
file.close();
int skip = 0;
switch (srcCode)
{
case UNKOWN:
break;
case ANSI:
break;
case UNICODE_LE:
skip = 2;
break;
case UNICODE_BE:
skip = 2;
break;
case UTF8_NOBOM:
break;
case UTF8_BOM:
skip = 3;
break;
case GBK:
break;
default:
break;
}
if (!file.open(QIODevice::WriteOnly | QIODevice::ExistingOnly | QIODevice::Truncate))
{
return CODE_ID::UNKOWN;
}
QByteArray text2Save;
if (skip == 2 && content.size() >= 2)
{
text2Save = QByteArray(content.mid(2));
}
else if (skip == 3 && content.size() >= 3)
{
text2Save = QByteArray(content.mid(3));
}
else
{
text2Save = QByteArray(content);
}
QString textOut;
Encode::tranStrToUNICODE(srcCode, text2Save.data(), text2Save.size(), textOut);
if (dstCode != UNKOWN)
{
//QByteArray codeFlag = Encode::getEncodeStartFlagByte(dstCode);
//20210822 发现大坑,转换到一定格式后,字符串前面自动带了标识,不需要再来检查一次
//if (!codeFlag.isEmpty())
//{
// //先写入标识头
// file.write(codeFlag);
//}
//如果编码是已知如下类型,则后续保存其它行时,不修改编码格式,继续按照原编码进行保存
//前面已经设置过编码了,这里不需要再设置
if (dstCode == CODE_ID::UTF8_BOM)
{
//自动转换不会带UTF-8 BOM,所以自己要在前面写个BOM头。这是一个例外。需要手动写入头
//其他必然BL LE则不需要。
QByteArray codeFlag = Encode::getEncodeStartFlagByte(dstCode);
if (!codeFlag.isEmpty())
{
//先写入标识头
file.write(codeFlag);
}
}
if (textOut.length() > 0)
{
//保存时注意编码问题。这个tolocal已经带了字符BOM头了。只要UTF8_BOM不会带
QByteArray t = textOut.toLocal8Bit();
file.write(textOut.toLocal8Bit());
}
}
file.close();
return dstCode;
}
CODE_ID EncodeConvert::getComboBoxCode(int index){
CODE_ID ret = CODE_ID::UNKOWN;
if (index < CODE_END)
{
ret = (CODE_ID)index;
}
return ret;
};
QFuture<EncodeThreadParameter_*> EncodeConvert::convertFileCode(QString filePath, QTreeWidgetItem* item)
{
EncodeThreadParameter_* p = new EncodeThreadParameter_(filePath);
p->item = item;
CODE_ID srcCode = static_cast<CODE_ID>(item->data(0, ITEM_CODE).toInt());
CODE_ID dstCode = getComboBoxCode(ui.codeToComboBox->currentIndex());
//int 0相等 1 不等
return commitTask([=](EncodeThreadParameter_* parameter)->EncodeThreadParameter_*
{
if (dstCode != CODE_ID::UNKOWN)
{
parameter->code = convertFileToCode(parameter->filepath, srcCode, dstCode);
}
else
{
parameter->code = UNKOWN;
}
return parameter;
}
, p);
}
//20220114 仅仅使用第一行失败编码还是不行,因为utf8和gbk其实有相同的编码范围。
//如果识别第一行为gbk的,则直接使用gbk。但是如果识别为utf8的,则需要识别更多的文本内容,这样会更慢
void EncodeConvert::scanFileCode()
{
m_finishCmpFileNums = 0;
m_commitCmpFileNums = 0;
ui.selectFileBt->setEnabled(false);
ui.startBt->setEnabled(false);
ui.closeBt->setEnabled(false);
ui.logTextBrowser->clear();
ui.logTextBrowser->append(tr("start scan file text code, please wait..."));
for (QList<fileAttriNode>::iterator iter = m_fileAttris.begin(); iter != m_fileAttris.end(); ++iter)
{
if (iter->type == RC_DIR)
{
iter->selfItem->setText(2, QString("--"));
}
else if ((iter->type == RC_FILE) && DocTypeListView::isSupportExt(fileSuffix(iter->relativePath)))
{
QFutureWatcher<EncodeThreadParameter_*>* futureWatcher = new QFutureWatcher<EncodeThreadParameter_*>();
QObject::connect(futureWatcher, &QFutureWatcher<EncodeThreadParameter_>::finished, this, &EncodeConvert::slot_scanFileCode);
futureWatcher->setFuture(this->checkFileCode(iter->relativePath,iter->selfItem));
++m_commitCmpFileNums;
}
else
{
iter->selfItem->setText(2, tr("ignore"));
}
}
int finishProcessRatio = 0;
while (m_finishCmpFileNums < m_commitCmpFileNums)
{
int curProcessRatio = m_finishCmpFileNums * 100 / m_commitCmpFileNums;
//没%5更新一下
if (curProcessRatio - finishProcessRatio >= 5)
{
finishProcessRatio = curProcessRatio;
ui.logTextBrowser->append(tr("please wait, total file %1,cur scan index %2, scan finish %3%").arg(m_commitCmpFileNums).arg(m_finishCmpFileNums).arg(curProcessRatio));
}
QCoreApplication::processEvents();
}
ui.logTextBrowser->append(tr("scan finished, total file %1").arg(m_commitCmpFileNums));
ui.selectFileBt->setEnabled(true);
ui.startBt->setEnabled(true);
ui.closeBt->setEnabled(true);
}
//文件对比完毕,显示出文件是否意义,不一样则红色字符标识
void EncodeConvert::slot_scanFileCode()
{
QFutureWatcher<EncodeThreadParameter_*>* s = dynamic_cast<QFutureWatcher<EncodeThreadParameter_*> *>(sender());
EncodeThreadParameter_* result = s->result();
//这里释放的内容,其实是在mode里面new出来的
if (result != nullptr)
{
result->item->setText(2, Encode::getCodeNameById(result->code));
result->item->setData(0, ITEM_CODE, result->code);
delete result;
result = nullptr;
}
delete s;
s = nullptr;
++m_finishCmpFileNums;
}
void EncodeConvert::slot_startConvert()
{
int extComboBoxIndex = ui.extComboBox->currentIndex();
CODE_ID dstCode = getComboBoxCode(ui.codeToComboBox->currentIndex());
m_finishCmpFileNums = 0;
m_commitCmpFileNums = 0;
ui.logTextBrowser->clear();
//如果编码是已知如下类型,则后续保存其它行时,不修改编码格式,继续按照原编码进行保存
QString destCodeName = Encode::getQtCodecNameById(dstCode);
if (destCodeName.isEmpty() || destCodeName == "unknown")
{
//这里永远不会走。因为界面上不会有未知选项
assert(false);
return;
}
else
{
QTextCodec::setCodecForLocale(QTextCodec::codecForName(destCodeName.toStdString().c_str()));
}
ui.selectFileBt->setEnabled(false);
ui.codeToComboBox->setEditable(false);
ui.closeBt->setEnabled(false);
for (QList<fileAttriNode>::iterator iter = m_fileAttris.begin(); iter != m_fileAttris.end(); ++iter)
{
if ((iter->type == RC_FILE) && isSupportExt(extComboBoxIndex, fileSuffix(iter->relativePath)))
{
qDebug() << iter->relativePath;
CODE_ID srcCode = static_cast<CODE_ID>(iter->selfItem->data(0, ITEM_CODE).toInt());
CODE_ID dstCode = getComboBoxCode(ui.codeToComboBox->currentIndex());
if (srcCode != dstCode)
{
QFutureWatcher<EncodeThreadParameter_*>* futureWatcher = new QFutureWatcher<EncodeThreadParameter_*>();
QObject::connect(futureWatcher, &QFutureWatcher<EncodeThreadParameter_>::finished, this, &EncodeConvert::slot_convertFileFinish);
futureWatcher->setFuture(this->convertFileCode(iter->relativePath, iter->selfItem));
++m_commitCmpFileNums;
}
else
{
iter->selfItem->setText(4, tr("already %1 ignore").arg(Encode::getCodeNameById(srcCode)));
}
}
else
{
iter->selfItem->setText(4, tr("ignore"));
}
}
int finishProcessRatio = 0;
while (m_finishCmpFileNums < m_commitCmpFileNums)
{
int curProcessRatio = m_finishCmpFileNums * 100 / m_commitCmpFileNums;
//没%5更新一下
if (curProcessRatio - finishProcessRatio >= 5)
{
finishProcessRatio = curProcessRatio;
ui.logTextBrowser->append(tr("total file %1,cur deal index %2,finish %3%").arg(m_commitCmpFileNums).arg(m_finishCmpFileNums).arg(curProcessRatio));
}
QCoreApplication::processEvents();
}
ui.logTextBrowser->append(tr("total file %1,cur deal index %2,finish 100%").arg(m_commitCmpFileNums).arg(m_finishCmpFileNums));
ui.logTextBrowser->append(tr("convert finished !"));
ui.selectFileBt->setEnabled(true);
ui.codeToComboBox->setEditable(true);
ui.closeBt->setEnabled(true);
}
//转换完成,设置当前表格上的显示状态
void EncodeConvert::slot_convertFileFinish()
{
QFutureWatcher<EncodeThreadParameter_*>* s = dynamic_cast<QFutureWatcher<EncodeThreadParameter_*> *>(sender());
EncodeThreadParameter_* result = s->result();
//这里释放的内容,其实是在mode里面new出来的
if (result != nullptr)
{
if (result->code != UNKOWN)
{
result->item->setText(3, Encode::getCodeNameById(result->code));
result->item->setText(4, tr("convert finish"));
}
else
{
result->item->setText(4, tr("convert fail"));
ui.logTextBrowser->append(tr("file %1 convert failed,pleas check...").arg(result->item->data(0, Qt::ToolTipRole).toString()));
}
result->item->setData(0, ITEM_CODE, result->code);
delete result;
result = nullptr;
}
delete s;
s = nullptr;
++m_finishCmpFileNums;
}
//对item进行间隔着色
void EncodeConvert::setItemIntervalBackground()
{
int curItemIndex = 0;
QTreeWidgetItemIterator it(ui.treeWidget);
while (*it) {
if (curItemIndex % 2 == 1)
{
setItemBackground(*it, QColor(0xf8faf9));
}
++it;
++curItemIndex;
}
}
void EncodeConvert::setItemBackground(QTreeWidgetItem* item, const QColor& color)
{
QBrush b(color);
item->setBackground(0, b);
item->setBackground(1, b);
item->setBackground(2, b);
item->setBackground(3, b);
item->setBackground(4, b);
}
void EncodeConvert::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasFormat("text/uri-list")) //只能打开文本文件
{
event->accept(); //可以在这个窗口部件上拖放对象
}
else
{
event->ignore();
}
}
void EncodeConvert::dropEvent(QDropEvent* e)
{
QList<QUrl> urls = e->mimeData()->urls();
if (urls.isEmpty())
return;
QString dirName = urls.first().toLocalFile();
if (dirName.isEmpty())
{
return;
}
QDir dir(dirName);
if (!dir.exists())
{
ui.logTextBrowser->append(tr("please drop a file dir ..."));
return;
}
ui.treeWidget->clear();
m_fileAttris.clear();
loadDir(dirName);
setItemIntervalBackground();
scanFileCode();
e->accept();
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C++
1
https://gitee.com/webion/notepad--.git
git@gitee.com:webion/notepad--.git
webion
notepad--
ndd
master

搜索帮助