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