File indexing completed on 2024-05-05 16:41:32
0001 /* 0002 SPDX-FileCopyrightText: 2011-2012 Sven Brauch <svenbrauch@googlemail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #ifndef PYCOMPLETIONHELPERS_H 0008 #define PYCOMPLETIONHELPERS_H 0009 0010 #include <language/duchain/declaration.h> 0011 0012 #include <QString> 0013 #include <QList> 0014 #include <QVariant> 0015 0016 #include "pythoncompletionexport.h" 0017 0018 using namespace KDevelop; 0019 0020 namespace Python { 0021 0022 void createArgumentList(Declaration* dec, QString& ret, QList<QVariant>* highlighting, 0023 int atArg = 0, bool includeTypes = true); 0024 0025 // export needed for unit tests 0026 KDEVPYTHONCOMPLETION_EXPORT int identifierMatchQuality(const QString& identifier1_, const QString& identifier2_); 0027 KDEVPYTHONCOMPLETION_EXPORT QString camelCaseToUnderscore(const QString& camelCase); 0028 0029 class TokenList; 0030 0031 class KDEVPYTHONCOMPLETION_EXPORT ExpressionParser { 0032 public: 0033 ExpressionParser(QString code); 0034 0035 enum Status { 0036 InvalidStatus, 0037 NothingFound, 0038 ExpressionFound, 0039 CommaFound, 0040 EventualCallFound, 0041 InitializerFound, 0042 FromFound, 0043 MemberAccessFound, 0044 ImportFound, 0045 GeneratorFound, 0046 RaiseFound, 0047 ForFound, 0048 ExceptFound, 0049 ColonFound, 0050 InFound, 0051 ClassFound, 0052 DefFound, 0053 EqualsFound, 0054 MeaninglessKeywordFound, // "and", "if", "return", ... 0055 NoCompletionKeywordFound // "for" 0056 }; 0057 0058 QString popExpression(Status* status); 0059 QString getRemainingCode(); 0060 QString getScannedCode(); 0061 QString skipUntilStatus(Status requestedStatus, bool* ok, int* expressionsSkipped = nullptr); 0062 TokenList popAll(); 0063 void reset(); 0064 int trailingWhitespace(); 0065 0066 private: 0067 QString m_code; 0068 int m_cursorPositionInString; 0069 }; 0070 0071 struct TokenListEntry { 0072 public: 0073 TokenListEntry(ExpressionParser::Status status_, QString expression_, int charOffset_) 0074 : status(status_) 0075 , expression(expression_) 0076 , charOffset(charOffset_) {}; 0077 ExpressionParser::Status status; 0078 QString expression; 0079 int charOffset; 0080 }; 0081 0082 class TokenList : public QList<TokenListEntry> { 0083 public: 0084 /** 0085 * @brief Reset the internal pointer to the last item, or offsetAtEnd items before 0086 **/ 0087 void reset(int offsetAtEnd = 0) { 0088 m_internalPtr = length() - offsetAtEnd; 0089 }; 0090 /** 0091 * @brief Set the internal pointer to "index". 0092 **/ 0093 void seek(int index) { 0094 m_internalPtr = index; 0095 }; 0096 /** 0097 * @brief Get the last item and advance the internal pointer. 0098 * 0099 * @return :TokenListEntry the item if the internal pointer is valid, an invalid item otherwise 0100 **/ 0101 TokenListEntry weakPop() { 0102 m_internalPtr --; 0103 if ( m_internalPtr < 0 ) { 0104 return TokenListEntry(ExpressionParser::InvalidStatus, QString(), -1); 0105 } 0106 return at(m_internalPtr); 0107 }; 0108 // First returned value is the *expression count* index, the second one is the *character count*. 0109 // The expressions count from the right, the characters count from the left. 0110 // (see PythonCodeCompletionContext::summonParentForEventualCall for an example why that makes sense) 0111 QPair<int, int> nextIndexOfStatus(ExpressionParser::Status status, int offsetFromEnd = 0) const { 0112 int currentIndex = length() - 1 - offsetFromEnd; 0113 while ( currentIndex >= 0 ) { 0114 if ( at(currentIndex).status == status ) { 0115 return QPair<int, int>(length() - currentIndex, at(currentIndex).charOffset); 0116 } 0117 currentIndex -= 1; 0118 } 0119 return QPair<int, int>(-1, -1); 0120 }; 0121 0122 QString toString() { 0123 QString ret; 0124 int pos = 0; 0125 foreach ( TokenListEntry item, *this ) { 0126 ret.append( 0127 "offset " + QString::number(item.charOffset) + 0128 " position " + QString::number(pos) + 0129 ": status " + QString::number(item.status) + 0130 ", expression " + item.expression + "\n" 0131 ); 0132 pos ++; 0133 } 0134 return ret; 0135 }; 0136 private: 0137 int m_internalPtr; 0138 }; 0139 0140 class ReplacementVariable; 0141 0142 struct RangeInString { 0143 RangeInString() : beginIndex(-1), endIndex(-1) {} 0144 RangeInString(int beginIndex_, int endIndex_) 0145 : beginIndex(beginIndex_) 0146 , endIndex(endIndex_) {} 0147 0148 bool isValid() 0149 { 0150 return beginIndex != -1 && endIndex != -1 && beginIndex < endIndex; 0151 } 0152 0153 int beginIndex, endIndex; 0154 }; 0155 0156 class KDEVPYTHONCOMPLETION_EXPORT StringFormatter { 0157 public: 0158 StringFormatter(const QString &string); 0159 0160 bool isInsideReplacementVariable(int cursorPosition) const; 0161 const ReplacementVariable *getReplacementVariable(int cursorPosition) const; 0162 RangeInString getVariablePosition(int cursorPosition) const; 0163 0164 int nextIdentifierId() const; 0165 0166 private: 0167 QString m_string; 0168 QList<ReplacementVariable> m_replacementVariables; 0169 QList<RangeInString> m_variablePositions; 0170 }; 0171 0172 class ReplacementVariable { 0173 0174 public: 0175 ReplacementVariable(QString identifier, QChar conversion = QChar(), QString formatSpec = QString()) 0176 : m_identifier(identifier), 0177 m_conversion(conversion), 0178 m_formatSpec(formatSpec) 0179 { 0180 } 0181 0182 const QString &identifier() const 0183 { 0184 return m_identifier; 0185 } 0186 0187 bool isIdentifierNumeric() const 0188 { 0189 bool isNumeric; 0190 m_identifier.toInt(&isNumeric); 0191 return isNumeric; 0192 } 0193 0194 const QChar &conversion() const 0195 { 0196 return m_conversion; 0197 } 0198 0199 bool hasConversion() const 0200 { 0201 return ! m_conversion.isNull(); 0202 } 0203 0204 const QString &formatSpec() const 0205 { 0206 return m_formatSpec; 0207 } 0208 0209 bool hasFormatSpec() const 0210 { 0211 return ! ( m_formatSpec.isNull() || m_formatSpec.isEmpty() ); 0212 } 0213 0214 // Convenience functions for extracting some of the parts of the format spec 0215 0216 QChar fillCharacter() const 0217 { 0218 return hasFillCharacter() ? m_formatSpec.at(0) : QChar(); 0219 } 0220 0221 bool hasFillCharacter() const 0222 { 0223 QStringList alignChars = QStringList() << "<" << ">" << "^" << "="; 0224 return hasAlign() && alignChars.indexOf(m_formatSpec.at(1)) != -1; 0225 } 0226 0227 QChar align() const 0228 { 0229 if ( hasAlign() ) { 0230 return hasFillCharacter() ? m_formatSpec.at(1) : m_formatSpec.at(0); 0231 } 0232 return QChar(); 0233 } 0234 0235 bool hasAlign() const 0236 { 0237 QRegExp regex("^.?[<>\\^=]"); 0238 return m_formatSpec.contains(regex); 0239 } 0240 0241 bool hasPrecision() const 0242 { 0243 if (fillCharacter() == '.') { 0244 return m_formatSpec.count('.') == 2; 0245 } 0246 return m_formatSpec.contains('.'); 0247 } 0248 0249 QChar type() const 0250 { 0251 return hasType() ? m_formatSpec.at(m_formatSpec.size() - 1) : QChar(); 0252 } 0253 0254 bool hasType() const 0255 { 0256 QStringList possibleTypes = QStringList() << "b" << "c" << "d" << "e" << "E" 0257 << "f" << "F" << "g" << "G" << "n" 0258 << "o" << "s" << "x" << "X" << "%"; 0259 0260 return (hasFormatSpec() && possibleTypes.indexOf(m_formatSpec.at(m_formatSpec.size() - 1)) != -1); 0261 } 0262 0263 QString toString() const 0264 { 0265 QString variable = "{" + m_identifier; 0266 if (hasConversion()) { 0267 variable += '!' + m_conversion; 0268 } 0269 if (hasFormatSpec()) { 0270 variable += ':' + m_formatSpec; 0271 } 0272 variable += "}"; 0273 0274 return variable; 0275 } 0276 0277 private: 0278 QString m_identifier; 0279 QChar m_conversion; 0280 QString m_formatSpec; 0281 }; 0282 0283 } 0284 0285 #endif