#include "snippetmgr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace vnotex; const QString SnippetMgr::c_snippetSymbolRegExp = QString("%([^%]+)%"); SnippetMgr::SnippetMgr() { loadSnippets(); } QString SnippetMgr::getSnippetFolder() const { return ConfigMgr::getInst().getUserSnippetFolder(); } const QVector> &SnippetMgr::getSnippets() const { return m_snippets; } void SnippetMgr::loadSnippets() { Q_ASSERT(m_snippets.isEmpty()); auto builtInSnippets = loadBuiltInSnippets(); QSet names; for (const auto &snippet : builtInSnippets) { Q_ASSERT(!names.contains(snippet->getName())); names.insert(snippet->getName()); } // Look for all the *.json files. QDir dir(getSnippetFolder()); dir.setFilter(QDir::Files | QDir::NoSymLinks); const auto jsonFiles = dir.entryList(QStringList() << "*.json"); for (const auto &jsonFile : jsonFiles) { auto snip = loadSnippet(dir.filePath(jsonFile)); if (snip->isValid()) { if (names.contains(snip->getName())) { qWarning() << "skip loading snippet with name conflict" << snip->getName() << jsonFile; continue; } names.insert(snip->getName()); addOneSnippet(snip); } } m_snippets.append(builtInSnippets); qDebug() << "loaded" << m_snippets.size() << "snippets"; } QVector SnippetMgr::getAvailableShortcuts(int p_exemption) const { QVector shortcuts; for (int i = 0; i < 100; ++i) { if (!m_shortcutToSnippet.contains(i) || i == p_exemption) { shortcuts.push_back(i); } } return shortcuts; } QSharedPointer SnippetMgr::find(const QString &p_name, Qt::CaseSensitivity p_cs) const { if (p_cs == Qt::CaseInsensitive) { const auto lowerName = p_name.toLower(); for (const auto &snip : m_snippets) { if (snip->getName().toLower() == lowerName) { return snip; } } } else { for (const auto &snip : m_snippets) { if (snip->getName() == p_name) { return snip; } } } return nullptr; } void SnippetMgr::addSnippet(const QSharedPointer &p_snippet) { Q_ASSERT(!find(p_snippet->getName(), Qt::CaseInsensitive)); saveSnippet(p_snippet); addOneSnippet(p_snippet); } void SnippetMgr::addOneSnippet(const QSharedPointer &p_snippet) { m_snippets.push_back(p_snippet); addSnippetToShortcutMap(p_snippet); } QSharedPointer SnippetMgr::loadSnippet(const QString &p_snippetFile) const { const auto obj = FileUtils::readJsonFile(p_snippetFile); auto snip = QSharedPointer::create(QFileInfo(p_snippetFile).completeBaseName()); snip->fromJson(obj); return snip; } void SnippetMgr::saveSnippet(const QSharedPointer &p_snippet) { Q_ASSERT(p_snippet->isValid() && !p_snippet->isReadOnly() && p_snippet->getType() != Snippet::Type::Dynamic); FileUtils::writeFile(getSnippetFile(p_snippet), p_snippet->toJson()); } void SnippetMgr::removeSnippet(const QString &p_name) { auto snippet = find(p_name); if (!snippet || snippet->isReadOnly()) { return; } removeSnippetFromShortcutMap(snippet); m_snippets.removeAll(snippet); FileUtils::removeFile(getSnippetFile(snippet)); } QString SnippetMgr::getSnippetFile(const QSharedPointer &p_snippet) const { return PathUtils::concatenateFilePath(getSnippetFolder(), p_snippet->getName() + QStringLiteral(".json")); } void SnippetMgr::updateSnippet(const QString &p_name, const QSharedPointer &p_snippet) { auto snippet = find(p_name); Q_ASSERT(snippet); // If renamed, remove the old file first. if (p_name != p_snippet->getName()) { FileUtils::removeFile(getSnippetFile(snippet)); } removeSnippetFromShortcutMap(snippet); *snippet = *p_snippet; saveSnippet(snippet); addSnippetToShortcutMap(snippet); } void SnippetMgr::removeSnippetFromShortcutMap(const QSharedPointer &p_snippet) { if (p_snippet->getShortcut() != Snippet::InvalidShortcut) { auto iter = m_shortcutToSnippet.find(p_snippet->getShortcut()); Q_ASSERT(iter != m_shortcutToSnippet.end()); if (iter.value() == p_snippet) { // There may exist conflict in shortcut. m_shortcutToSnippet.erase(iter); } } } void SnippetMgr::addSnippetToShortcutMap(const QSharedPointer &p_snippet) { if (p_snippet->getShortcut() != Snippet::InvalidShortcut) { m_shortcutToSnippet.insert(p_snippet->getShortcut(), p_snippet); } } void SnippetMgr::applySnippet(const QString &p_name, vte::VTextEdit *p_textEdit, const OverrideMap &p_overrides) const { auto snippet = find(p_name); if (!snippet) { return; } Q_ASSERT(snippet->isValid()); auto cursor = p_textEdit->textCursor(); cursor.beginEditBlock(); // Get selected text. const auto selectedText = p_textEdit->selectedText(); p_textEdit->removeSelectedText(); QString appliedText; int cursorOffset = 0; auto it = p_overrides.find(p_name); if (it != p_overrides.end()) { appliedText = it.value(); cursorOffset = appliedText.size(); } else { // Fetch indentation of first line. QString indentationSpaces; if (snippet->isIndentAsFirstLineEnabled()) { indentationSpaces = vte::TextEditUtils::fetchIndentationSpaces(cursor.block()); } appliedText = snippet->apply(selectedText, indentationSpaces, cursorOffset); appliedText = applySnippetBySymbol(appliedText, selectedText, cursorOffset, p_overrides); } const int beforePos = cursor.position(); cursor.insertText(appliedText); cursor.setPosition(beforePos + cursorOffset); cursor.endEditBlock(); p_textEdit->setTextCursor(cursor); } QString SnippetMgr::applySnippetBySymbol(const QString &p_content) const { int offset = 0; return applySnippetBySymbol(p_content, QString(), offset); } QString SnippetMgr::applySnippetBySymbol(const QString &p_content, const QString &p_selectedText, int &p_cursorOffset, const OverrideMap &p_overrides) const { QString content(p_content); int maxTimes = 100; QRegularExpression regExp(c_snippetSymbolRegExp); int pos = 0; while (pos < content.size() && maxTimes-- > 0) { QRegularExpressionMatch match; int idx = content.indexOf(regExp, pos, &match); if (idx == -1) { break; } const auto snippetName = match.captured(1); auto snippet = find(snippetName); if (!snippet) { // Skip it. pos = idx + match.capturedLength(0); continue; } QString afterText; auto it = p_overrides.find(snippetName); if (it != p_overrides.end()) { afterText = it.value(); } else { const auto indentationSpaces = vte::TextUtils::fetchIndentationSpacesInMultiLines(content, idx); // Ignore the cursor mark. int ignoredCursorOffset = 0; afterText = snippet->apply(p_selectedText, indentationSpaces, ignoredCursorOffset); } content.replace(idx, match.capturedLength(0), afterText); // Maintain the cursor offset. if (p_cursorOffset > idx) { if (p_cursorOffset < idx + match.capturedLength(0)) { p_cursorOffset = idx; } else { p_cursorOffset += (afterText.size() - match.capturedLength(0)); } } // @afterText may still contains snippet symbol. pos = idx; } return content; } // Used as the function template for some date/time related dynamic snippets. static QString formattedDateTime(const QString &p_format) { return QDateTime::currentDateTime().toString(p_format); } QVector> SnippetMgr::loadBuiltInSnippets() const { QVector> snippets; addDynamicSnippet(snippets, "d", tr("the day as number without a leading zero (`1` to `31`)"), std::bind(formattedDateTime, "d")); addDynamicSnippet(snippets, "dd", tr("the day as number with a leading zero (`01` to `31`)"), std::bind(formattedDateTime, "dd")); addDynamicSnippet(snippets, "ddd", tr("the abbreviated localized day name (e.g. `Mon` to `Sun`)"), std::bind(formattedDateTime, "ddd")); addDynamicSnippet(snippets, "dddd", tr("the long localized day name (e.g. `Monday` to `Sunday`)"), std::bind(formattedDateTime, "dddd")); addDynamicSnippet(snippets, "M", tr("the month as number without a leading zero (`1` to `12`)"), std::bind(formattedDateTime, "M")); addDynamicSnippet(snippets, "MM", tr("the month as number with a leading zero (`01` to `12`)"), std::bind(formattedDateTime, "MM")); addDynamicSnippet(snippets, "MMM", tr("the abbreviated localized month name (e.g. `Jan` to `Dec`)"), std::bind(formattedDateTime, "MMM")); addDynamicSnippet(snippets, "MMMM", tr("the long localized month name (e.g. `January` to `December`)"), std::bind(formattedDateTime, "MMMM")); addDynamicSnippet(snippets, "yy", tr("the year as two digit numbers (`00` to `99`)"), std::bind(formattedDateTime, "yy")); addDynamicSnippet(snippets, "yyyy", tr("the year as four digit numbers"), std::bind(formattedDateTime, "yyyy")); addDynamicSnippet(snippets, "w", tr("the week number (`1` to `53`)"), [](const QString &) { return QString::number(QDate::currentDate().weekNumber()); }); addDynamicSnippet(snippets, "ww", tr("the week number with a leading zero (`01` to `53`)"), [](const QString &) { return Utils::intToString(QDate::currentDate().weekNumber(), 2); }); addDynamicSnippet(snippets, "H", tr("the hour without a leading zero (`0` to `23` even with AM/PM display)"), std::bind(formattedDateTime, "H")); addDynamicSnippet(snippets, "HH", tr("the hour with a leading zero (`00` to `23` even with AM/PM display)"), std::bind(formattedDateTime, "HH")); addDynamicSnippet(snippets, "m", tr("the minute without a leading zero (`0` to `59`)"), std::bind(formattedDateTime, "m")); addDynamicSnippet(snippets, "mm", tr("the minute with a leading zero (`00` to `59`)"), std::bind(formattedDateTime, "mm")); addDynamicSnippet(snippets, "s", tr("the second without a leading zero (`0` to `59`)"), std::bind(formattedDateTime, "s")); addDynamicSnippet(snippets, "ss", tr("the second with a leading zero (`00` to `59`)"), std::bind(formattedDateTime, "ss")); addDynamicSnippet(snippets, "date", tr("date (`2021-02-24`)"), std::bind(formattedDateTime, "yyyy-MM-dd")); addDynamicSnippet(snippets, "da", tr("the abbreviated date (`20210224`)"), std::bind(formattedDateTime, "yyyyMMdd")); addDynamicSnippet(snippets, "time", tr("time (`16:51:02`)"), std::bind(formattedDateTime, "hh:mm:ss")); addDynamicSnippet(snippets, "datetime", tr("date and time (`2021-02-24_16:51:02`)"), std::bind(formattedDateTime, "yyyy-MM-dd_hh:mm:ss")); // These snippets need override to fill the real value. // Check generateOverrides(). addDynamicSnippet(snippets, QStringLiteral("note"), tr("name of current note"), [](const QString &) { return tr("[Value Not Available]"); }); addDynamicSnippet(snippets, QStringLiteral("no"), tr("complete base name of current note"), [](const QString &) { return tr("[Value Not Available]"); }); return snippets; } void SnippetMgr::addDynamicSnippet(QVector> &p_snippets, const QString &p_name, const QString &p_description, const DynamicSnippet::Callback &p_callback) { auto snippet = QSharedPointer::create(p_name, p_description, p_callback); p_snippets.push_back(snippet); } SnippetMgr::OverrideMap SnippetMgr::generateOverrides(const Buffer *p_buffer) { OverrideMap overrides; overrides.insert(QStringLiteral("note"), p_buffer->getName()); overrides.insert(QStringLiteral("no"), QFileInfo(p_buffer->getName()).completeBaseName()); return overrides; } SnippetMgr::OverrideMap SnippetMgr::generateOverrides(const QString &p_fileName) { OverrideMap overrides; overrides.insert(QStringLiteral("note"), p_fileName); overrides.insert(QStringLiteral("no"), QFileInfo(p_fileName).completeBaseName()); return overrides; }