File indexing completed on 2025-02-02 15:48:02
0001 // clang-format off 0002 /* 0003 * KDiff3 - Text Diff And Merge Tool 0004 * 0005 * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de 0006 * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com 0007 * SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 // clang-format on 0010 #include "Utils.h" 0011 0012 #include "compat.h" 0013 #include "fileaccess.h" 0014 #include "TypeUtils.h" 0015 0016 #include <QString> 0017 #include <QStringList> 0018 #include <QHash> 0019 #include <QRegularExpression> 0020 0021 /* Split the command line into arguments. 0022 * Normally split at white space separators except when quoting with " or '. 0023 * Backslash is treated as meta. 0024 * Detect parsing errors like unclosed quotes. 0025 * The first item in the list will be the command itself. 0026 * Returns the error reason as string or an empty string on success. 0027 * Eg. >"1" "2"< => >1<, >2< 0028 * Eg. >'\'\\'< => >'\< backslash is a meta character 0029 * Eg. > "\\" < => >\< 0030 * Eg. >"c:\sed" 's/a/\' /g'< => >c:\sed<, >s/a/' /g< 0031 */ 0032 QString Utils::getArguments(QString cmd, QString& program, QStringList& args) 0033 { 0034 program = QString(); 0035 args.clear(); 0036 for(QtSizeType i = 0; i < cmd.length(); ++i) 0037 { 0038 while(i < cmd.length() && cmd[i].isSpace()) 0039 { 0040 ++i; 0041 } 0042 if(cmd[i] == '"' || cmd[i] == '\'') // argument beginning with a quote 0043 { 0044 QChar quoteChar = cmd[i]; 0045 ++i; 0046 QtSizeType argStart = i; 0047 bool bSkip = false; 0048 while(i < cmd.length() && (cmd[i] != quoteChar || bSkip)) 0049 { 0050 if(bSkip) 0051 { 0052 bSkip = false; 0053 //Don't emulate bash here we are not talking to it. 0054 //For us all quotes are the same. 0055 if(cmd[i] == '\\' || cmd[i] == '\'' || cmd[i] == '"') 0056 { 0057 cmd.remove(i - 1, 1); // remove the backslash '\' 0058 continue; 0059 } 0060 } 0061 else if(cmd[i] == '\\') 0062 bSkip = true; 0063 ++i; 0064 } 0065 if(i < cmd.length()) 0066 { 0067 args << cmd.mid(argStart, i - argStart); 0068 if(i + 1 < cmd.length() && !cmd[i + 1].isSpace()) 0069 return i18n("Expecting space after closing quote."); 0070 } 0071 else 0072 return i18n("Unmatched quote."); 0073 continue; 0074 } 0075 else 0076 { 0077 QtSizeType argStart = i; 0078 while(i < cmd.length() && (!cmd[i].isSpace() /*|| bSkip*/)) 0079 { 0080 if(cmd[i] == '"' || cmd[i] == '\'') 0081 return i18n("Unexpected quote character within argument."); 0082 ++i; 0083 } 0084 args << cmd.mid(argStart, i - argStart); 0085 } 0086 } 0087 if(args.isEmpty()) 0088 return i18n("No program specified."); 0089 else 0090 { 0091 program = args[0]; 0092 args.pop_front(); 0093 } 0094 return QString(); 0095 } 0096 0097 bool Utils::wildcardMultiMatch(const QString& wildcard, const QString& testString, bool bCaseSensitive) 0098 { 0099 static QHash<QString, QRegularExpression> s_patternMap; 0100 0101 const QStringList regExpList = wildcard.split(QChar(';')); 0102 0103 for(const QString& regExp : regExpList) 0104 { 0105 QHash<QString, QRegularExpression>::iterator patIt = s_patternMap.find(regExp); 0106 if(patIt == s_patternMap.end()) 0107 { 0108 QRegularExpression pattern(QRegularExpression::wildcardToRegularExpression(regExp), bCaseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); 0109 patIt = s_patternMap.insert(regExp, pattern); 0110 } 0111 0112 if(patIt.value().match(testString).hasMatch()) 0113 return true; 0114 } 0115 0116 return false; 0117 } 0118 0119 //TODO: Only used by calcTokenPos. 0120 bool Utils::isCTokenChar(QChar c) 0121 { 0122 return (c == '_') || 0123 (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || 0124 (c >= '0' && c <= '9'); 0125 } 0126 0127 0128 //TODO: Needed? Only user of isCTokenChar. 0129 /// Calculate where a token starts and ends, given the x-position on screen. 0130 void Utils::calcTokenPos(const QString& s, qint32 posOnScreen, QtSizeType& pos1, QtSizeType& pos2) 0131 { 0132 QtSizeType pos = std::max(0, posOnScreen); 0133 if(pos >= s.length()) 0134 { 0135 pos1 = s.length(); 0136 pos2 = s.length(); 0137 return; 0138 } 0139 0140 pos1 = pos; 0141 pos2 = pos + 1; 0142 0143 if(isCTokenChar(s[pos1])) 0144 { 0145 while(pos1 >= 0 && isCTokenChar(s[pos1])) 0146 --pos1; 0147 ++pos1; 0148 0149 while(pos2 < s.length() && isCTokenChar(s[pos2])) 0150 ++pos2; 0151 } 0152 } 0153 0154 QString Utils::calcHistoryLead(const QString& s) 0155 { 0156 static const QRegularExpression nonWhitespace("\\S"), whitespace("\\s"); 0157 0158 // Return the start of the line until the first white char after the first non white char. 0159 QtSizeType i = s.indexOf(nonWhitespace); 0160 if(i == -1) 0161 return QString(""); 0162 0163 i = s.indexOf(whitespace, i); 0164 if(Q_UNLIKELY(i == -1)) 0165 return s;// Very unlikely 0166 0167 return s.left(i); 0168 } 0169 0170 /* 0171 QUrl::toLocalFile does some special handling for locally visable windows network drives. 0172 If QUrl::isLocal however it returns false and we get an empty string back. 0173 */ 0174 QString Utils::urlToString(const QUrl &url) 0175 { 0176 if(!FileAccess::isLocal(url)) 0177 return url.toString(); 0178 0179 QString result = url.toLocalFile(); 0180 if(result.isEmpty()) 0181 return url.path(); 0182 0183 return result; 0184 } 0185