File indexing completed on 2024-04-28 15:30:32

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 "kateconfig.h"
0013 #include "katedocument.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 QVector<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     const auto views = KTextEditor::EditorPrivate::self()->views();
0171     for (auto *view : views) {
0172         if (view->statusBar() && view->statusBar()->modeMenu()) {
0173             view->statusBar()->modeMenu()->reloadItems();
0174         }
0175     }
0176 }
0177 
0178 //
0179 // save the given list to config file + update
0180 //
0181 void KateModeManager::save(const QList<KateFileType *> &v)
0182 {
0183     KConfig katerc(QStringLiteral("katemoderc"), KConfig::NoGlobals);
0184 
0185     QStringList newg;
0186     newg.reserve(v.size());
0187     for (const KateFileType *type : v) {
0188         KConfigGroup config(&katerc, type->name);
0189 
0190         config.writeEntry("Section", type->section);
0191         config.writeXdgListEntry("Wildcards", type->wildcards);
0192         config.writeXdgListEntry("Mimetypes", type->mimetypes);
0193         config.writeEntry("Priority", type->priority);
0194         config.writeEntry("Indenter", type->indenter);
0195 
0196         QString varLine = type->varLine;
0197         if (!varLine.contains(QLatin1String("kate:"))) {
0198             varLine.prepend(QLatin1String("kate: "));
0199         }
0200 
0201         config.writeEntry("Variables", varLine);
0202 
0203         config.writeEntry("Highlighting", type->hl);
0204 
0205         // only for generated types...
0206         config.writeEntry("Highlighting Generated", type->hlGenerated);
0207         config.writeEntry("Highlighting Version", type->version);
0208 
0209         newg << type->name;
0210     }
0211 
0212     const auto groupNames = katerc.groupList();
0213     for (const QString &groupName : groupNames) {
0214         if (newg.indexOf(groupName) == -1) {
0215             katerc.deleteGroup(groupName);
0216         }
0217     }
0218 
0219     katerc.sync();
0220 
0221     update();
0222 }
0223 
0224 QString KateModeManager::fileType(KTextEditor::DocumentPrivate *doc, const QString &fileToReadFrom)
0225 {
0226     if (!doc) {
0227         return QString();
0228     }
0229 
0230     if (m_types.isEmpty()) {
0231         return QString();
0232     }
0233 
0234     QString fileName = doc->url().toString();
0235     int length = doc->url().toString().length();
0236 
0237     QString result;
0238 
0239     // Try wildcards
0240     if (!fileName.isEmpty()) {
0241         static const QLatin1String commonSuffixes[] = {
0242             QLatin1String(".orig"),
0243             QLatin1String(".new"),
0244             QLatin1String("~"),
0245             QLatin1String(".bak"),
0246             QLatin1String(".BAK"),
0247         };
0248 
0249         if (!(result = wildcardsFind(fileName)).isEmpty()) {
0250             return result;
0251         }
0252 
0253         QString backupSuffix = KateDocumentConfig::global()->backupSuffix();
0254         if (fileName.endsWith(backupSuffix)) {
0255             if (!(result = wildcardsFind(fileName.left(length - backupSuffix.length()))).isEmpty()) {
0256                 return result;
0257             }
0258         }
0259 
0260         for (auto &commonSuffix : commonSuffixes) {
0261             if (commonSuffix != backupSuffix && fileName.endsWith(commonSuffix)) {
0262                 if (!(result = wildcardsFind(fileName.left(length - commonSuffix.size()))).isEmpty()) {
0263                     return result;
0264                 }
0265             }
0266         }
0267     }
0268 
0269     // either read the file passed to this function (pre-load) or use the normal mimeType() KF KTextEditor API
0270     QString mtName;
0271     if (!fileToReadFrom.isEmpty()) {
0272         mtName = QMimeDatabase().mimeTypeForFile(fileToReadFrom).name();
0273     } else {
0274         mtName = doc->mimeType();
0275     }
0276     return mimeTypesFind(mtName);
0277 }
0278 
0279 template<typename UnaryStringPredicate>
0280 static QString findHighestPriorityTypeNameIf(const QList<KateFileType *> &types, QStringList KateFileType::*list, UnaryStringPredicate anyOfCondition)
0281 {
0282     const KateFileType *match = nullptr;
0283     auto matchPriority = std::numeric_limits<int>::lowest();
0284     for (const KateFileType *type : types) {
0285         if (type->priority > matchPriority && std::any_of((type->*list).cbegin(), (type->*list).cend(), anyOfCondition)) {
0286             match = type;
0287             matchPriority = type->priority;
0288         }
0289     }
0290     return match == nullptr ? QString() : match->name;
0291 }
0292 
0293 QString KateModeManager::wildcardsFind(const QString &fileName) const
0294 {
0295     const auto fileNameNoPath = QFileInfo{fileName}.fileName();
0296     return findHighestPriorityTypeNameIf(m_types, &KateFileType::wildcards, [&fileNameNoPath](const QString &wildcard) {
0297         return KSyntaxHighlighting::WildcardMatcher::exactMatch(fileNameNoPath, wildcard);
0298     });
0299 }
0300 
0301 QString KateModeManager::mimeTypesFind(const QString &mimeTypeName) const
0302 {
0303     return findHighestPriorityTypeNameIf(m_types, &KateFileType::mimetypes, [&mimeTypeName](const QString &name) {
0304         return mimeTypeName == name;
0305     });
0306 }
0307 
0308 const KateFileType &KateModeManager::fileType(const QString &name) const
0309 {
0310     for (int i = 0; i < m_types.size(); ++i) {
0311         if (m_types[i]->name == name) {
0312             return *m_types[i];
0313         }
0314     }
0315 
0316     static KateFileType notype;
0317     return notype;
0318 }