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 << ∥
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 ∥
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