1 #include "sqlitesyntaxhighlighter.h"
2 #include "parser/lexer.h"
3 #include "services/config.h"
4 #include "style.h"
5 #include "parser/keywords.h"
6 #include <QTextDocument>
7 #include <QDebug>
8 #include <QPlainTextEdit>
9 #include <QApplication>
10 #include <QStyle>
11 
SqliteSyntaxHighlighter(QTextDocument * parent)12 SqliteSyntaxHighlighter::SqliteSyntaxHighlighter(QTextDocument *parent) :
13     QSyntaxHighlighter(parent)
14 {
15     setupFormats();
16     setupMapping();
17     setCurrentBlockState(regulartTextBlockState);
18     connect(CFG, SIGNAL(massSaveCommitted()), this, SLOT(setupFormats()));
19 }
20 
setFormat(SqliteSyntaxHighlighter::State state,QTextCharFormat format)21 void SqliteSyntaxHighlighter::setFormat(SqliteSyntaxHighlighter::State state, QTextCharFormat format)
22 {
23     formats[state] = format;
24 }
25 
getFormat(SqliteSyntaxHighlighter::State state) const26 QTextCharFormat SqliteSyntaxHighlighter::getFormat(SqliteSyntaxHighlighter::State state) const
27 {
28     return formats[state];
29 }
30 
setupFormats()31 void SqliteSyntaxHighlighter::setupFormats()
32 {
33     QTextCharFormat format;
34 
35     // Standard
36     format.setForeground(QApplication::style()->standardPalette().text());
37     format.setFontWeight(QFont::Normal);
38     format.setFontItalic(false);
39     formats[State::STANDARD] = format;
40 
41     // Parenthesis
42     format.setForeground(QApplication::style()->standardPalette().text());
43     formats[State::PARENTHESIS] = format;
44 
45     // String
46     format.setForeground(STYLE->extendedPalette().editorString());
47     format.setFontWeight(QFont::Normal);
48     format.setFontItalic(true);
49     formats[State::STRING] = format;
50 
51     // Keyword
52     format.setForeground(QApplication::style()->standardPalette().windowText());
53     format.setFontWeight(QFont::ExtraBold);
54     format.setFontItalic(false);
55     formats[State::KEYWORD] = format;
56 
57     // BindParam
58     format.setForeground(QApplication::style()->standardPalette().linkVisited());
59     format.setFontWeight(QFont::Normal);
60     format.setFontItalic(false);
61     formats[State::BIND_PARAM] = format;
62 
63     // Blob
64     format.setForeground(QApplication::style()->standardPalette().text());
65     format.setFontWeight(QFont::Normal);
66     format.setFontItalic(false);
67     formats[State::BLOB] = format;
68 
69     // Comment
70     format.setForeground(QApplication::style()->standardPalette().dark());
71     format.setFontWeight(QFont::Normal);
72     format.setFontItalic(true);
73     formats[State::COMMENT] = format;
74 
75     // Number
76     format.setForeground(QApplication::style()->standardPalette().text());
77     format.setFontWeight(QFont::Normal);
78     format.setFontItalic(false);
79     formats[State::NUMBER] = format;
80 }
81 
setupMapping()82 void SqliteSyntaxHighlighter::setupMapping()
83 {
84     tokenTypeMapping[Token::STRING] = State::STRING;
85     tokenTypeMapping[Token::COMMENT] = State::COMMENT;
86     tokenTypeMapping[Token::FLOAT] = State::NUMBER;
87     tokenTypeMapping[Token::INTEGER] = State::NUMBER;
88     tokenTypeMapping[Token::BIND_PARAM] = State::BIND_PARAM;
89     tokenTypeMapping[Token::PAR_LEFT] = State::PARENTHESIS;
90     tokenTypeMapping[Token::PAR_RIGHT] = State::PARENTHESIS;
91     tokenTypeMapping[Token::BLOB] = State::BLOB;
92     tokenTypeMapping[Token::KEYWORD] = State::KEYWORD;
93 }
94 
getPreviousStatePrefix(TextBlockState textBlockState)95 QString SqliteSyntaxHighlighter::getPreviousStatePrefix(TextBlockState textBlockState)
96 {
97     QString prefix = "";
98     switch (textBlockState)
99     {
100         case SqliteSyntaxHighlighter::TextBlockState::REGULAR:
101             break;
102         case SqliteSyntaxHighlighter::TextBlockState::BLOB:
103             prefix = "x'";
104             break;
105         case SqliteSyntaxHighlighter::TextBlockState::STRING:
106             prefix = "'";
107             break;
108         case SqliteSyntaxHighlighter::TextBlockState::COMMENT:
109             prefix = "/*";
110             break;
111         case SqliteSyntaxHighlighter::TextBlockState::ID_1:
112             prefix = "[";
113             break;
114         case SqliteSyntaxHighlighter::TextBlockState::ID_2:
115             prefix = "\"";
116             break;
117         case SqliteSyntaxHighlighter::TextBlockState::ID_3:
118             prefix = "`";
119             break;
120     }
121     return prefix;
122 }
123 
highlightBlock(const QString & text)124 void SqliteSyntaxHighlighter::highlightBlock(const QString &text)
125 {
126     if (text.length() <= 0 || document()->characterCount() > MAX_QUERY_LENGTH)
127         return;
128 
129     // Reset to default
130     QSyntaxHighlighter::setFormat(0, text.length(), formats[State::STANDARD]);
131 
132     qint32 idxModifier = 0;
133     QString statePrefix = "";
134     if (previousBlockState() != regulartTextBlockState)
135     {
136         statePrefix = getPreviousStatePrefix(static_cast<TextBlockState>(previousBlockState()));
137         idxModifier += statePrefix.size();
138     }
139 
140     Lexer lexer;
141     lexer.setTolerantMode(true);
142     lexer.prepare(statePrefix+text);
143 
144     // Previous error state.
145     // Empty lines have no userData, so we will look for any previous paragraph that is
146     // valid and has a data, so it has any logical meaning to highlighter.
147     QTextBlock prevBlock = currentBlock().previous();
148     while ((!prevBlock.isValid() || !prevBlock.userData() || prevBlock.text().isEmpty()) && prevBlock.position() > 0)
149         prevBlock = prevBlock.previous();
150 
151     TextBlockData* prevData = nullptr;
152     if (prevBlock.isValid())
153         prevData = dynamic_cast<TextBlockData*>(prevBlock.userData());
154 
155     TextBlockData* data = new TextBlockData();
156     int errorStart = -1;
157     TokenPtr token = lexer.getToken();
158     TokenPtr aheadToken;
159     while (token)
160     {
161         aheadToken = lexer.getToken();
162 
163         if (handleToken(token, aheadToken, idxModifier, errorStart, data, prevData))
164             errorStart = token->start + currentBlock().position();
165 
166         if (data->getEndsWithQuerySeparator())
167             errorStart = -1;
168 
169         handleParenthesis(token, data);
170         token = aheadToken;
171     }
172 
173     setCurrentBlockUserData(data);
174 }
175 
handleToken(TokenPtr token,TokenPtr aheadToken,qint32 idxModifier,int errorStart,TextBlockData * currBlockData,TextBlockData * previousBlockData)176 bool SqliteSyntaxHighlighter::handleToken(TokenPtr token, TokenPtr aheadToken, qint32 idxModifier, int errorStart, TextBlockData* currBlockData,
177                                           TextBlockData* previousBlockData)
178 {
179     qint64 start = token->start - idxModifier;
180     qint64 lgt = token->end - token->start + 1;
181     if (start < 0)
182     {
183         lgt += start; // cut length by num of chars before 0 (after idxModifier applied)
184         start = 0;
185     }
186 
187     if (createTriggerContext && token->type == Token::OTHER && (token->value.toLower() == "old" || token->value.toLower() == "new"))
188         token->type = Token::KEYWORD;
189 
190     if (aheadToken && aheadToken->type == Token::PAR_LEFT && token->type == Token::KEYWORD && isSoftKeyword(token->value))
191         token->type = Token::OTHER;
192 
193     bool limitedDamage = false;
194     bool querySeparator = (token->type == Token::Type::OPERATOR && token->value == ";");
195     bool error = isError(start, lgt, &limitedDamage);
196     bool valid = isValid(start, lgt);
197     bool wasError = (
198                         (errorStart > -1) &&
199                         (start + currentBlock().position() + lgt >= errorStart) &&
200                         !currBlockData->getEndsWithQuerySeparator() // if it was set for previous token in the same block
201                     ) ||
202                     (
203                         token->start == 0 &&
204                         previousBlockData &&
205                         previousBlockData->getEndsWithError() &&
206                         !previousBlockData->getEndsWithQuerySeparator()
207                     );
208     bool fatalError = (error && !limitedDamage) || wasError;
209 
210     QTextCharFormat format = formats[State::STANDARD];
211 
212     // Applying valid object format.
213     applyValidObjectFormat(format, valid, error, wasError);
214 
215     // Get format for token type (if any)
216     if (tokenTypeMapping.contains(token->type))
217         format = formats[tokenTypeMapping[token->type]];
218 
219     // Merge with error format (if this is an error).
220     applyErrorFormat(format, error, wasError, token->type);
221 
222     // Apply format
223     QSyntaxHighlighter::setFormat(start, lgt, format);
224 
225     // Save block state
226     TolerantTokenPtr tolerantToken = token.dynamicCast<TolerantToken>();
227     if (tolerantToken->invalid)
228         setStateForUnfinishedToken(tolerantToken);
229     else
230         setCurrentBlockState(regulartTextBlockState);
231 
232     currBlockData->setEndsWithError(fatalError);
233     currBlockData->setEndsWithQuerySeparator(querySeparator);
234 
235     return fatalError;
236 }
237 
applyErrorFormat(QTextCharFormat & format,bool isError,bool wasError,Token::Type tokenType)238 void SqliteSyntaxHighlighter::applyErrorFormat(QTextCharFormat& format, bool isError, bool wasError, Token::Type tokenType)
239 {
240     if ((!isError && !wasError) || tokenType == Token::Type::COMMENT)
241         return;
242 
243     format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
244     format.setUnderlineColor(QColor(Qt::red));
245 }
246 
applyValidObjectFormat(QTextCharFormat & format,bool isValid,bool isError,bool wasError)247 void SqliteSyntaxHighlighter::applyValidObjectFormat(QTextCharFormat& format, bool isValid, bool isError, bool wasError)
248 {
249     if (isError || wasError || !isValid)
250         return;
251 
252     format.setForeground(QApplication::style()->standardPalette().link());
253     if (objectLinksEnabled)
254         format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
255 }
256 
handleParenthesis(TokenPtr token,TextBlockData * data)257 void SqliteSyntaxHighlighter::handleParenthesis(TokenPtr token, TextBlockData* data)
258 {
259     if (token->type == Token::PAR_LEFT || token->type == Token::PAR_RIGHT)
260         data->insertParenthesis(currentBlock().position() + token->start, token->value[0].toLatin1());
261 }
getCreateTriggerContext() const262 bool SqliteSyntaxHighlighter::getCreateTriggerContext() const
263 {
264     return createTriggerContext;
265 }
266 
setCreateTriggerContext(bool value)267 void SqliteSyntaxHighlighter::setCreateTriggerContext(bool value)
268 {
269     createTriggerContext = value;
270 }
271 
getObjectLinksEnabled() const272 bool SqliteSyntaxHighlighter::getObjectLinksEnabled() const
273 {
274     return objectLinksEnabled;
275 }
276 
setObjectLinksEnabled(bool value)277 void SqliteSyntaxHighlighter::setObjectLinksEnabled(bool value)
278 {
279     objectLinksEnabled = value;
280 }
281 
isError(int start,int lgt,bool * limitedDamage)282 bool SqliteSyntaxHighlighter::isError(int start, int lgt, bool* limitedDamage)
283 {
284     start += currentBlock().position();
285     int end = start + lgt - 1;
286     for (const Error& error : errors)
287     {
288         if (error.from <= start && error.to >= end)
289         {
290             *limitedDamage = error.limitedDamage;
291             return true;
292         }
293     }
294     return false;
295 }
296 
isValid(int start,int lgt)297 bool SqliteSyntaxHighlighter::isValid(int start, int lgt)
298 {
299     start += currentBlock().position();
300     int end = start + lgt - 1;
301     for (const DbObject& obj : dbObjects)
302     {
303         if (obj.from <= start && obj.to >= end)
304             return true;
305     }
306     return false;
307 }
308 
setStateForUnfinishedToken(TolerantTokenPtr tolerantToken)309 void SqliteSyntaxHighlighter::setStateForUnfinishedToken(TolerantTokenPtr tolerantToken)
310 {
311     switch (tolerantToken->type)
312     {
313         case Token::OTHER:
314         {
315             switch (tolerantToken->value.at(0).toLatin1())
316             {
317                 case '[':
318                     setCurrentBlockState(static_cast<int>(TextBlockState::ID_1));
319                     break;
320                 case '"':
321                     setCurrentBlockState(static_cast<int>(TextBlockState::ID_2));
322                     break;
323                 case '`':
324                     setCurrentBlockState(static_cast<int>(TextBlockState::ID_3));
325                     break;
326             }
327             break;
328         }
329         case Token::STRING:
330             setCurrentBlockState(static_cast<int>(TextBlockState::STRING));
331             break;
332         case Token::COMMENT:
333             setCurrentBlockState(static_cast<int>(TextBlockState::COMMENT));
334             break;
335         case Token::BLOB:
336             setCurrentBlockState(static_cast<int>(TextBlockState::BLOB));
337             break;
338         default:
339             break;
340     }
341 }
clearErrors()342 void SqliteSyntaxHighlighter::clearErrors()
343 {
344     errors.clear();
345 }
346 
haveErrors()347 bool SqliteSyntaxHighlighter::haveErrors()
348 {
349     return errors.count() > 0;
350 }
351 
addDbObject(int from,int to)352 void SqliteSyntaxHighlighter::addDbObject(int from, int to)
353 {
354     dbObjects << DbObject(from, to);
355 }
356 
clearDbObjects()357 void SqliteSyntaxHighlighter::clearDbObjects()
358 {
359     dbObjects.clear();
360 }
361 
addError(int from,int to,bool limitedDamage)362 void SqliteSyntaxHighlighter::addError(int from, int to, bool limitedDamage)
363 {
364     errors << Error(from, to, limitedDamage);
365 }
366 
Error(int from,int to,bool limitedDamage)367 SqliteSyntaxHighlighter::Error::Error(int from, int to, bool limitedDamage) :
368     from(from), to(to), limitedDamage(limitedDamage)
369 {
370 }
371 
qHash(SqliteSyntaxHighlighter::State state)372 int qHash(SqliteSyntaxHighlighter::State state)
373 {
374     return static_cast<int>(state);
375 }
376 
377 
DbObject(int from,int to)378 SqliteSyntaxHighlighter::DbObject::DbObject(int from, int to) :
379     from(from), to(to)
380 {
381 }
382 
parentheses()383 QList<const TextBlockData::Parenthesis*> TextBlockData::parentheses()
384 {
385     QList<const TextBlockData::Parenthesis*> list;
386     for (const TextBlockData::Parenthesis& par : parData)
387         list << &par;
388 
389     return list;
390 }
391 
insertParenthesis(int pos,char c)392 void TextBlockData::insertParenthesis(int pos, char c)
393 {
394     Parenthesis par;
395     par.character = c;
396     par.position = pos;
397     parData << par;
398 }
399 
parenthesisForPosision(int pos)400 const TextBlockData::Parenthesis* TextBlockData::parenthesisForPosision(int pos)
401 {
402     for (const Parenthesis& par : parData)
403     {
404         if (par.position == pos)
405             return &par;
406     }
407     return nullptr;
408 }
getEndsWithError() const409 bool TextBlockData::getEndsWithError() const
410 {
411     return endsWithError;
412 }
413 
setEndsWithError(bool value)414 void TextBlockData::setEndsWithError(bool value)
415 {
416     endsWithError = value;
417 }
getEndsWithQuerySeparator() const418 bool TextBlockData::getEndsWithQuerySeparator() const
419 {
420     return endsWithQuerySeparator;
421 }
422 
setEndsWithQuerySeparator(bool value)423 void TextBlockData::setEndsWithQuerySeparator(bool value)
424 {
425     endsWithQuerySeparator = value;
426 }
427 
428 
operator ==(const TextBlockData::Parenthesis & other)429 int TextBlockData::Parenthesis::operator==(const TextBlockData::Parenthesis& other)
430 {
431     return other.position == position && other.character == character;
432 }
433 
getLanguageName() const434 QString SqliteHighlighterPlugin::getLanguageName() const
435 {
436     return "SQL";
437 }
438 
createSyntaxHighlighter(QWidget * textEdit) const439 QSyntaxHighlighter* SqliteHighlighterPlugin::createSyntaxHighlighter(QWidget* textEdit) const
440 {
441     QPlainTextEdit* plainEdit = dynamic_cast<QPlainTextEdit*>(textEdit);
442     if (plainEdit)
443         return new SqliteSyntaxHighlighter(plainEdit->document());
444 
445     QTextEdit* edit = dynamic_cast<QTextEdit*>(textEdit);
446     if (edit)
447         return new SqliteSyntaxHighlighter(edit->document());
448 
449     return nullptr;
450 }
451