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