1 /* 2 SPDX-FileCopyrightText: 2011-2012 Sven Brauch <svenbrauch@googlemail.com> 3 4 SPDX-License-Identifier: GPL-2.0-or-later 5 */ 6 7 #ifndef PYCOMPLETIONHELPERS_H 8 #define PYCOMPLETIONHELPERS_H 9 10 #include <language/duchain/declaration.h> 11 12 #include <QString> 13 #include <QList> 14 #include <QVariant> 15 16 #include "pythoncompletionexport.h" 17 18 using namespace KDevelop; 19 20 namespace Python { 21 22 void createArgumentList(Declaration* dec, QString& ret, QList<QVariant>* highlighting, 23 int atArg = 0, bool includeTypes = true); 24 25 // export needed for unit tests 26 KDEVPYTHONCOMPLETION_EXPORT int identifierMatchQuality(const QString& identifier1_, const QString& identifier2_); 27 KDEVPYTHONCOMPLETION_EXPORT QString camelCaseToUnderscore(const QString& camelCase); 28 29 class TokenList; 30 31 class KDEVPYTHONCOMPLETION_EXPORT ExpressionParser { 32 public: 33 ExpressionParser(QString code); 34 35 enum Status { 36 InvalidStatus, 37 NothingFound, 38 ExpressionFound, 39 CommaFound, 40 EventualCallFound, 41 InitializerFound, 42 FromFound, 43 MemberAccessFound, 44 ImportFound, 45 GeneratorFound, 46 RaiseFound, 47 ForFound, 48 ExceptFound, 49 ColonFound, 50 InFound, 51 ClassFound, 52 DefFound, 53 EqualsFound, 54 MeaninglessKeywordFound, // "and", "if", "return", ... 55 NoCompletionKeywordFound // "for" 56 }; 57 58 QString popExpression(Status* status); 59 QString getRemainingCode(); 60 QString getScannedCode(); 61 QString skipUntilStatus(Status requestedStatus, bool* ok, int* expressionsSkipped = nullptr); 62 TokenList popAll(); 63 void reset(); 64 int trailingWhitespace(); 65 66 private: 67 QString m_code; 68 int m_cursorPositionInString; 69 }; 70 71 struct TokenListEntry { 72 public: TokenListEntryTokenListEntry73 TokenListEntry(ExpressionParser::Status status_, QString expression_, int charOffset_) 74 : status(status_) 75 , expression(expression_) 76 , charOffset(charOffset_) {}; 77 ExpressionParser::Status status; 78 QString expression; 79 int charOffset; 80 }; 81 82 class TokenList : public QList<TokenListEntry> { 83 public: 84 /** 85 * @brief Reset the internal pointer to the last item, or offsetAtEnd items before 86 **/ 87 void reset(int offsetAtEnd = 0) { 88 m_internalPtr = length() - offsetAtEnd; 89 }; 90 /** 91 * @brief Set the internal pointer to "index". 92 **/ seek(int index)93 void seek(int index) { 94 m_internalPtr = index; 95 }; 96 /** 97 * @brief Get the last item and advance the internal pointer. 98 * 99 * @return :TokenListEntry the item if the internal pointer is valid, an invalid item otherwise 100 **/ weakPop()101 TokenListEntry weakPop() { 102 m_internalPtr --; 103 if ( m_internalPtr < 0 ) { 104 return TokenListEntry(ExpressionParser::InvalidStatus, QString(), -1); 105 } 106 return at(m_internalPtr); 107 }; 108 // First returned value is the *expression count* index, the second one is the *character count*. 109 // The expressions count from the right, the characters count from the left. 110 // (see PythonCodeCompletionContext::summonParentForEventualCall for an example why that makes sense) 111 QPair<int, int> nextIndexOfStatus(ExpressionParser::Status status, int offsetFromEnd = 0) const { 112 int currentIndex = length() - 1 - offsetFromEnd; 113 while ( currentIndex >= 0 ) { 114 if ( at(currentIndex).status == status ) { 115 return QPair<int, int>(length() - currentIndex, at(currentIndex).charOffset); 116 } 117 currentIndex -= 1; 118 } 119 return QPair<int, int>(-1, -1); 120 }; 121 toString()122 QString toString() { 123 QString ret; 124 int pos = 0; 125 foreach ( TokenListEntry item, *this ) { 126 ret.append( 127 "offset " + QString::number(item.charOffset) + 128 " position " + QString::number(pos) + 129 ": status " + QString::number(item.status) + 130 ", expression " + item.expression + "\n" 131 ); 132 pos ++; 133 } 134 return ret; 135 }; 136 private: 137 int m_internalPtr; 138 }; 139 140 class ReplacementVariable; 141 142 struct RangeInString { RangeInStringRangeInString143 RangeInString() : beginIndex(-1), endIndex(-1) {} RangeInStringRangeInString144 RangeInString(int beginIndex_, int endIndex_) 145 : beginIndex(beginIndex_) 146 , endIndex(endIndex_) {} 147 isValidRangeInString148 bool isValid() 149 { 150 return beginIndex != -1 && endIndex != -1 && beginIndex < endIndex; 151 } 152 153 int beginIndex, endIndex; 154 }; 155 156 class KDEVPYTHONCOMPLETION_EXPORT StringFormatter { 157 public: 158 StringFormatter(const QString &string); 159 160 bool isInsideReplacementVariable(int cursorPosition) const; 161 const ReplacementVariable *getReplacementVariable(int cursorPosition) const; 162 RangeInString getVariablePosition(int cursorPosition) const; 163 164 int nextIdentifierId() const; 165 166 private: 167 QString m_string; 168 QList<ReplacementVariable> m_replacementVariables; 169 QList<RangeInString> m_variablePositions; 170 }; 171 172 class ReplacementVariable { 173 174 public: 175 ReplacementVariable(QString identifier, QChar conversion = QChar(), QString formatSpec = QString()) m_identifier(identifier)176 : m_identifier(identifier), 177 m_conversion(conversion), 178 m_formatSpec(formatSpec) 179 { 180 } 181 identifier()182 const QString &identifier() const 183 { 184 return m_identifier; 185 } 186 isIdentifierNumeric()187 bool isIdentifierNumeric() const 188 { 189 bool isNumeric; 190 m_identifier.toInt(&isNumeric); 191 return isNumeric; 192 } 193 conversion()194 const QChar &conversion() const 195 { 196 return m_conversion; 197 } 198 hasConversion()199 bool hasConversion() const 200 { 201 return ! m_conversion.isNull(); 202 } 203 formatSpec()204 const QString &formatSpec() const 205 { 206 return m_formatSpec; 207 } 208 hasFormatSpec()209 bool hasFormatSpec() const 210 { 211 return ! ( m_formatSpec.isNull() || m_formatSpec.isEmpty() ); 212 } 213 214 // Convenience functions for extracting some of the parts of the format spec 215 fillCharacter()216 QChar fillCharacter() const 217 { 218 return hasFillCharacter() ? m_formatSpec.at(0) : QChar(); 219 } 220 hasFillCharacter()221 bool hasFillCharacter() const 222 { 223 QStringList alignChars = QStringList() << "<" << ">" << "^" << "="; 224 return hasAlign() && alignChars.indexOf(m_formatSpec.at(1)) != -1; 225 } 226 align()227 QChar align() const 228 { 229 if ( hasAlign() ) { 230 return hasFillCharacter() ? m_formatSpec.at(1) : m_formatSpec.at(0); 231 } 232 return QChar(); 233 } 234 hasAlign()235 bool hasAlign() const 236 { 237 QRegExp regex("^.?[<>\\^=]"); 238 return m_formatSpec.contains(regex); 239 } 240 hasPrecision()241 bool hasPrecision() const 242 { 243 if (fillCharacter() == '.') { 244 return m_formatSpec.count('.') == 2; 245 } 246 return m_formatSpec.contains('.'); 247 } 248 type()249 QChar type() const 250 { 251 return hasType() ? m_formatSpec.at(m_formatSpec.size() - 1) : QChar(); 252 } 253 hasType()254 bool hasType() const 255 { 256 QStringList possibleTypes = QStringList() << "b" << "c" << "d" << "e" << "E" 257 << "f" << "F" << "g" << "G" << "n" 258 << "o" << "s" << "x" << "X" << "%"; 259 260 return (hasFormatSpec() && possibleTypes.indexOf(m_formatSpec.at(m_formatSpec.size() - 1)) != -1); 261 } 262 toString()263 QString toString() const 264 { 265 QString variable = "{" + m_identifier; 266 if (hasConversion()) { 267 variable += '!' + m_conversion; 268 } 269 if (hasFormatSpec()) { 270 variable += ':' + m_formatSpec; 271 } 272 variable += "}"; 273 274 return variable; 275 } 276 277 private: 278 QString m_identifier; 279 QChar m_conversion; 280 QString m_formatSpec; 281 }; 282 283 } 284 285 #endif 286