File indexing completed on 2024-04-14 03:55:11

0001 /*
0002     SPDX-FileCopyrightText: 2001-2010 Christoph Cullmann <cullmann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 // BEGIN Includes
0008 #include "katemodemanager.h"
0009 #include "katemodemenulist.h"
0010 #include "katestatusbar.h"
0011 
0012 #include "document.h"
0013 #include "kateconfig.h"
0014 #include "kateglobal.h"
0015 #include "katepartdebug.h"
0016 #include "katesyntaxmanager.h"
0017 #include "kateview.h"
0018 
0019 #include <KConfigGroup>
0020 #include <KSyntaxHighlighting/WildcardMatcher>
0021 
0022 #include <QFileInfo>
0023 #include <QMimeDatabase>
0024 
0025 #include <algorithm>
0026 #include <limits>
0027 // END Includes
0028 
0029 static QStringList vectorToList(const QList<QString> &v)
0030 {
0031     QStringList l;
0032     l.reserve(v.size());
0033     std::copy(v.begin(), v.end(), std::back_inserter(l));
0034     return l;
0035 }
0036 
0037 KateModeManager::KateModeManager()
0038 {
0039     update();
0040 }
0041 
0042 KateModeManager::~KateModeManager()
0043 {
0044     qDeleteAll(m_types);
0045 }
0046 
0047 bool compareKateFileType(const KateFileType *const left, const KateFileType *const right)
0048 {
0049     int comparison = left->translatedSection.compare(right->translatedSection, Qt::CaseInsensitive);
0050     if (comparison == 0) {
0051         comparison = left->translatedName.compare(right->translatedName, Qt::CaseInsensitive);
0052     }
0053     return comparison < 0;
0054 }
0055 
0056 //
0057 // read the types from config file and update the internal list
0058 //
0059 void KateModeManager::update()
0060 {
0061     KConfig config(QStringLiteral("katemoderc"), KConfig::NoGlobals);
0062 
0063     QStringList g(config.groupList());
0064 
0065     KateFileType *normalType = nullptr;
0066     qDeleteAll(m_types);
0067     m_types.clear();
0068     m_name2Type.clear();
0069     for (int z = 0; z < g.count(); z++) {
0070         KConfigGroup cg(&config, g[z]);
0071 
0072         KateFileType *type = new KateFileType();
0073         type->name = g[z];
0074         type->wildcards = cg.readXdgListEntry(QStringLiteral("Wildcards"));
0075         type->mimetypes = cg.readXdgListEntry(QStringLiteral("Mimetypes"));
0076         type->priority = cg.readEntry(QStringLiteral("Priority"), 0);
0077         type->varLine = cg.readEntry(QStringLiteral("Variables"));
0078         type->indenter = cg.readEntry(QStringLiteral("Indenter"));
0079 
0080         type->hl = cg.readEntry(QStringLiteral("Highlighting"));
0081 
0082         // only for generated types...
0083         type->hlGenerated = cg.readEntry(QStringLiteral("Highlighting Generated"), false);
0084 
0085         // the "Normal" mode will be added later
0086         if (type->name == QLatin1String("Normal")) {
0087             if (!normalType) {
0088                 normalType = type;
0089             }
0090         } else {
0091             type->section = cg.readEntry(QStringLiteral("Section"));
0092             type->version = cg.readEntry(QStringLiteral("Highlighting Version"));
0093 
0094             // already add all non-highlighting generated user types
0095             if (!type->hlGenerated) {
0096                 m_types.append(type);
0097             }
0098         }
0099 
0100         // insert into the hash...
0101         // NOTE: "katemoderc" could have modes that do not exist or are invalid (for example, custom
0102         // XML files that were deleted or renamed), so they will be added to the list "m_types" later
0103         m_name2Type.insert(type->name, type);
0104     }
0105 
0106     // try if the hl stuff is up to date...
0107     const auto modes = KateHlManager::self()->modeList();
0108     for (int i = 0; i < modes.size(); ++i) {
0109         // filter out hidden languages; and
0110         // filter out "None" hl, we add that later as "Normal" mode.
0111         // Hl with empty names will also be filtered. The
0112         // KTextEditor::DocumentPrivate::updateFileType() function considers
0113         // hl with empty names as invalid.
0114         if (modes[i].isHidden() || modes[i].name().isEmpty() || modes[i].name() == QLatin1String("None")) {
0115             continue;
0116         }
0117 
0118         KateFileType *type = nullptr;
0119         bool newType = false;
0120         if (m_name2Type.contains(modes[i].name())) {
0121             type = m_name2Type[modes[i].name()];
0122 
0123             // add if hl generated, we skipped that stuff above
0124             if (type->hlGenerated) {
0125                 m_types.append(type);
0126             }
0127         } else {
0128             newType = true;
0129             type = new KateFileType();
0130             type->name = modes[i].name();
0131             type->priority = 0;
0132             type->hlGenerated = true;
0133             m_types.append(type);
0134             m_name2Type.insert(type->name, type);
0135         }
0136 
0137         if (newType || type->version != QString::number(modes[i].version())) {
0138             type->name = modes[i].name();
0139             type->section = modes[i].section();
0140             type->wildcards = vectorToList(modes[i].extensions());
0141             type->mimetypes = vectorToList(modes[i].mimeTypes());
0142             type->priority = modes[i].priority();
0143             type->version = QString::number(modes[i].version());
0144             type->indenter = modes[i].indenter();
0145             type->hl = modes[i].name();
0146         }
0147 
0148         type->translatedName = modes[i].translatedName();
0149         type->translatedSection = modes[i].translatedSection();
0150     }
0151 
0152     // sort the list...
0153     std::sort(m_types.begin(), m_types.end(), compareKateFileType);
0154 
0155     // add the none type...
0156     if (!normalType) {
0157         normalType = new KateFileType();
0158     }
0159 
0160     // marked by hlGenerated
0161     normalType->name = QStringLiteral("Normal");
0162     normalType->translatedName = i18n("Normal");
0163     normalType->hl = QStringLiteral("None");
0164     normalType->hlGenerated = true;
0165 
0166     m_types.prepend(normalType);
0167 
0168     // update the mode menu of the status bar, for all views.
0169     // this menu uses the KateFileType objects
0170     for (auto *view : KTextEditor::EditorPrivate::self()->views()) {
0171         if (view->statusBar() && view->statusBar()->modeMenu()) {
0172             view->statusBar()->modeMenu()->reloadItems();
0173         }
0174     }
0175 }
0176 
0177 //
0178 // save the given list to config file + update
0179 //
0180 void KateModeManager::save(const QList<KateFileType *> &v)
0181 {
0182     KConfig katerc(QStringLiteral("katemoderc"), KConfig::NoGlobals);
0183 
0184     QStringList newg;
0185     newg.reserve(v.size());
0186     for (const KateFileType *type : v) {
0187         KConfigGroup config(&katerc, type->name);
0188 
0189         config.writeEntry("Section", type->section);
0190         config.writeXdgListEntry("Wildcards", type->wildcards);
0191         config.writeXdgListEntry("Mimetypes", type->mimetypes);
0192         config.writeEntry("Priority", type->priority);
0193         config.writeEntry("Indenter", type->indenter);
0194 
0195         QString varLine = type->varLine;
0196         if (!varLine.contains(QLatin1String("kate:"))) {
0197             varLine.prepend(QLatin1String("kate: "));
0198         }
0199 
0200         config.writeEntry("Variables", varLine);
0201 
0202         config.writeEntry("Highlighting", type->hl);
0203 
0204         // only for generated types...
0205         config.writeEntry("Highlighting Generated", type->hlGenerated);
0206         config.writeEntry("Highlighting Version", type->version);
0207 
0208         newg << type->name;
0209     }
0210 
0211     const auto groupNames = katerc.groupList();
0212     for (const QString &groupName : groupNames) {
0213         if (newg.indexOf(groupName) == -1) {
0214             katerc.deleteGroup(groupName);
0215         }
0216     }
0217 
0218     katerc.sync();
0219 
0220     update();
0221 }
0222 
0223 QString KateModeManager::fileType(KTextEditor::Document *doc, const QString &fileToReadFrom)
0224 {
0225     if (!doc) {
0226         return QString();
0227     }
0228 
0229     if (m_types.isEmpty()) {
0230         return QString();
0231     }
0232 
0233     QString fileName = doc->url().toString();
0234     int length = doc->url().toString().length();
0235 
0236     QString result;
0237 
0238     // Try wildcards
0239     if (!fileName.isEmpty()) {
0240         static const QLatin1String commonSuffixes[] = {
0241             QLatin1String(".orig"),
0242             QLatin1String(".new"),
0243             QLatin1String("~"),
0244             QLatin1String(".bak"),
0245             QLatin1String(".BAK"),
0246         };
0247 
0248         if (!(result = wildcardsFind(fileName)).isEmpty()) {
0249             return result;
0250         }
0251 
0252         QString backupSuffix = KateDocumentConfig::global()->backupSuffix();
0253         if (fileName.endsWith(backupSuffix)) {
0254             if (!(result = wildcardsFind(fileName.left(length - backupSuffix.length()))).isEmpty()) {
0255                 return result;
0256             }
0257         }
0258 
0259         for (auto &commonSuffix : commonSuffixes) {
0260             if (commonSuffix != backupSuffix && fileName.endsWith(commonSuffix)) {
0261                 if (!(result = wildcardsFind(fileName.left(length - commonSuffix.size()))).isEmpty()) {
0262                     return result;
0263                 }
0264             }
0265         }
0266     }
0267 
0268     // either read the file passed to this function (pre-load) or use the normal mimeType() KF KTextEditor API
0269     QString mtName;
0270     if (!fileToReadFrom.isEmpty()) {
0271         mtName = QMimeDatabase().mimeTypeForFile(fileToReadFrom).name();
0272     } else {
0273         mtName = doc->mimeType();
0274     }
0275     return mimeTypesFind(mtName);
0276 }
0277 
0278 template<typename UnaryStringPredicate>
0279 static QString findHighestPriorityTypeNameIf(const QList<KateFileType *> &types, QStringList KateFileType::*list, UnaryStringPredicate anyOfCondition)
0280 {
0281     const KateFileType *match = nullptr;
0282     auto matchPriority = std::numeric_limits<int>::lowest();
0283     for (const KateFileType *type : types) {
0284         if (type->priority > matchPriority && std::any_of((type->*list).cbegin(), (type->*list).cend(), anyOfCondition)) {
0285             match = type;
0286             matchPriority = type->priority;
0287         }
0288     }
0289     return match == nullptr ? QString() : match->name;
0290 }
0291 
0292 QString KateModeManager::wildcardsFind(const QString &fileName) const
0293 {
0294     const auto fileNameNoPath = QFileInfo{fileName}.fileName();
0295     return findHighestPriorityTypeNameIf(m_types, &KateFileType::wildcards, [&fileNameNoPath](const QString &wildcard) {
0296         return KSyntaxHighlighting::WildcardMatcher::exactMatch(fileNameNoPath, wildcard);
0297     });
0298 }
0299 
0300 QString KateModeManager::mimeTypesFind(const QString &mimeTypeName) const
0301 {
0302     return findHighestPriorityTypeNameIf(m_types, &KateFileType::mimetypes, [&mimeTypeName](const QString &name) {
0303         return mimeTypeName == name;
0304     });
0305 }
0306 
0307 const KateFileType &KateModeManager::fileType(const QString &name) const
0308 {
0309     for (int i = 0; i < m_types.size(); ++i) {
0310         if (m_types[i]->name == name) {
0311             return *m_types[i];
0312         }
0313     }
0314 
0315     static KateFileType notype;
0316     return notype;
0317 }