scide: avoid recursive calls to Main::instance
[supercollider.git] / editors / sc-ide / widgets / code_editor / highlighter.cpp
blobdbcb51aee6c3d92378b4786630f3cd71458afce6
1 /*
2 SuperCollider Qt IDE
3 Copyright (c) 2012 Jakob Leben & Tim Blechmann
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <cassert>
22 #include <algorithm>
24 #include "highlighter.hpp"
25 #include "../../core/main.hpp"
26 #include "../../core/settings/manager.hpp"
28 #include <QApplication>
30 namespace ScIDE {
32 SyntaxHighlighterGlobals * SyntaxHighlighterGlobals::mInstance = 0;
34 SyntaxHighlighterGlobals::SyntaxHighlighterGlobals( Main *main, Settings::Manager * settings ):
35 QObject(main)
37 Q_ASSERT(mInstance == 0);
38 mInstance = this;
40 initSyntaxRules();
42 // initialize formats from settings:
43 applySettings(settings);
45 connect(main, SIGNAL(applySettingsRequest(Settings::Manager*)),
46 this, SLOT(applySettings(Settings::Manager*)));
49 void SyntaxHighlighterGlobals::initSyntaxRules()
51 /* NOTE:
53 The highlighting algorithm demands that all regexps
54 start with a caret "^", to only match at beginning of string.
56 Order is important:
57 -- floatRegexp is subset of radixFloatRegex -> must come later
58 -- classRegexp and primitiveRegexp are subsets of symbolArgRegexp -> must come later
62 mInCodeRules << SyntaxRule( Token::WhiteSpace, "^\\s+" );
64 initKeywords();
65 initBuiltins();
67 mInCodeRules << SyntaxRule( Token::SymbolArg, "^\\b[A-Za-z_]\\w*\\:" );
69 mInCodeRules << SyntaxRule( Token::Name, "^[a-z]\\w*" );
71 mInCodeRules << SyntaxRule( Token::Class, "^\\b[A-Z]\\w*" );
73 mInCodeRules << SyntaxRule( Token::Primitive, "^\\b_\\w+" );
75 mInCodeRules << SyntaxRule( Token::Symbol, "^(\\\\\\w*|\\'([^\\'\\\\]*(\\\\.[^\\'\\\\]*)*)\\')" );
77 mInCodeRules << SyntaxRule( Token::Char, "^\\$\\\\?." );
79 mInCodeRules << SyntaxRule( Token::String, "^\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\"" );
81 mInCodeRules << SyntaxRule( Token::EnvVar, "^~\\w+" );
83 mInCodeRules << SyntaxRule( Token::RadixFloat, "^\\b\\d+r[0-9a-zA-Z]*(\\.[0-9A-Z]*)?" );
85 // do not include leading "-" in float highlighting, as there's no clear
86 // rule whether it is not rather a binary operator
87 mInCodeRules << SyntaxRule( Token::Float, "^\\b((\\d+(\\.\\d+)?([eE][-+]?\\d+)?(pi)?)|pi)" );
89 mInCodeRules << SyntaxRule( Token::HexInt, "^\\b0(x|X)(\\d|[a-f]|[A-F])+" );
91 mInCodeRules << SyntaxRule( Token::SingleLineComment, "^//[^\r\n]*" );
93 mInCodeRules << SyntaxRule( Token::MultiLineCommentStart, "^/\\*" );
95 mInSymbolRegexp.setPattern("([^\'\\\\]*(\\\\.[^\'\\\\]*)*)");
96 mInStringRegexp.setPattern("([^\"\\\\]*(\\\\.[^\"\\\\]*)*)");
99 void SyntaxHighlighterGlobals::initKeywords()
101 QStringList keywords;
102 keywords << "arg"
103 << "classvar"
104 << "const"
105 << "super"
106 << "this"
107 << "var";
109 QString keywordPattern = QString("^\\b(%1)\\b").arg(keywords.join("|"));
110 mInCodeRules << SyntaxRule(Token::Keyword, keywordPattern);
113 void SyntaxHighlighterGlobals::initBuiltins()
115 QStringList builtins;
116 builtins << "false"
117 << "inf"
118 << "nil"
119 << "true"
120 << "thisFunction"
121 << "thisFunctionDef"
122 << "thisMethod"
123 << "thisProcess"
124 << "thisThread"
125 << "currentEnvironment"
126 << "topEnvironment"
129 QString builtinsPattern = QString("^\\b(%1)\\b").arg(builtins.join("|"));
130 mInCodeRules << SyntaxRule(Token::Builtin, builtinsPattern);
133 void SyntaxHighlighterGlobals::applySettings( Settings::Manager *s )
135 QString key("IDE/editor/highlighting");
136 applySettings( s, key + "/normal", PlainFormat );
137 applySettings( s, key + "/keyword", KeywordFormat );
138 applySettings( s, key + "/built-in", BuiltinFormat );
139 applySettings( s, key + "/primitive", PrimitiveFormat );
140 applySettings( s, key + "/class", ClassFormat );
141 applySettings( s, key + "/number", NumberFormat );
142 applySettings( s, key + "/symbol", SymbolFormat );
143 applySettings( s, key + "/env-var", EnvVarFormat );
144 applySettings( s, key + "/string", StringFormat );
145 applySettings( s, key + "/char", CharFormat );
146 applySettings( s, key + "/comment", CommentFormat );
148 Q_EMIT(syntaxFormatsChanged());
151 void SyntaxHighlighterGlobals::applySettings( Settings::Manager *s, const QString &key, SyntaxFormat type )
153 mFormats[type] = s->value(key).value<QTextCharFormat>();
156 SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent):
157 QSyntaxHighlighter( parent )
159 mGlobals = SyntaxHighlighterGlobals::instance();
161 connect(mGlobals, SIGNAL(syntaxFormatsChanged()), this, SLOT(rehighlight()));
164 Token::Type SyntaxHighlighter::findMatchingRule (const QString& text, int& currentIndex, int& lengthOfMatch)
166 int matchLength = -1;
167 Token::Type matchType = Token::Unknown;
169 QVector<SyntaxRule>::const_iterator it = mGlobals->mInCodeRules.constBegin();
170 QVector<SyntaxRule>::const_iterator end = mGlobals->mInCodeRules.constEnd();
172 for (; it != end; ++it) {
173 SyntaxRule const & rule = *it;
174 int matchIndex = rule.expr.indexIn(text, currentIndex, QRegExp::CaretAtOffset);
175 // a guard to ensure all regexps match only at beginning of string:
176 assert(matchIndex <= currentIndex);
177 if (matchIndex != -1) {
178 matchType = rule.type;
179 matchLength = rule.expr.matchedLength();
180 break;
184 lengthOfMatch = matchType == Token::Unknown ? 0 : matchLength;
185 return matchType;
188 void SyntaxHighlighter::highlightBlockInCode(const QString& text, int & currentIndex, int & currentState)
190 TextBlockData *blockData = static_cast<TextBlockData*>(currentBlockUserData());
191 Q_ASSERT(blockData);
193 const QTextCharFormat * formats = mGlobals->formats();
195 do {
196 static QString openingBrackets("({[");
197 static QString closingBrackets(")}]");
198 static QChar stringStart('\"');
199 static QChar symbolStart('\'');
201 QChar currentChar = text[currentIndex];
203 if (currentChar == stringStart) {
204 currentState = inString;
205 setFormat(currentIndex, 1, formats[StringFormat]);
206 blockData->tokens.push_back( Token(Token::String, currentIndex) );
207 currentIndex += 1;
208 return;
211 if (currentChar == symbolStart) {
212 currentState = inSymbol;
213 setFormat(currentIndex, 1, formats[SymbolFormat]);
214 blockData->tokens.push_back( Token(Token::Symbol, currentIndex) );
215 currentIndex += 1;
216 return;
219 if (openingBrackets.contains(currentChar)) {
220 blockData->tokens.push_back(
221 Token( Token::OpeningBracket,
222 currentIndex, 1,
223 currentChar.toAscii() ) );
224 ++currentIndex;
225 continue;
228 if (closingBrackets.contains(currentChar)) {
229 blockData->tokens.push_back(
230 Token( Token::ClosingBracket,
231 currentIndex, 1,
232 currentChar.toAscii() ) );
233 ++currentIndex;
234 continue;
237 int lenghtOfMatch;
238 Token::Type tokenType = findMatchingRule(text, currentIndex, lenghtOfMatch);
240 switch (tokenType)
242 case Token::WhiteSpace:
243 // avoid inserting a token
244 currentIndex += lenghtOfMatch;
245 continue;
247 case Token::Class:
248 setFormat(currentIndex, lenghtOfMatch, formats[ClassFormat]);
249 break;
251 case Token::Builtin:
252 setFormat(currentIndex, lenghtOfMatch, formats[BuiltinFormat]);
253 break;
255 case Token::Primitive:
256 setFormat(currentIndex, lenghtOfMatch, formats[PrimitiveFormat]);
257 break;
259 case Token::Keyword:
260 setFormat(currentIndex, lenghtOfMatch, formats[KeywordFormat]);
261 break;
263 case Token::Symbol:
264 setFormat(currentIndex, lenghtOfMatch, formats[SymbolFormat]);
265 break;
267 case Token::SymbolArg:
268 // Omit the trailing ":" that was included in the regexp:
269 setFormat(currentIndex, lenghtOfMatch-1, formats[SymbolFormat]);
270 break;
272 case Token::EnvVar:
273 setFormat(currentIndex, lenghtOfMatch, formats[EnvVarFormat]);
274 break;
276 case Token::String:
277 setFormat(currentIndex, lenghtOfMatch, formats[StringFormat]);
278 break;
280 case Token::Char:
281 setFormat(currentIndex, lenghtOfMatch, formats[CharFormat]);
282 break;
284 case Token::Float:
285 case Token::HexInt:
286 case Token::RadixFloat:
287 setFormat(currentIndex, lenghtOfMatch, formats[NumberFormat]);
288 break;
290 case Token::SingleLineComment:
291 setFormat(currentIndex, lenghtOfMatch, formats[CommentFormat]);
292 currentIndex += lenghtOfMatch;
293 continue;
295 case Token::MultiLineCommentStart:
296 setFormat(currentIndex, lenghtOfMatch, formats[CommentFormat]);
297 currentIndex += lenghtOfMatch;
298 currentState = inComment;
299 return;
301 case Token::Unknown:
302 blockData->tokens.push_back( Token(Token::Unknown, currentIndex, 1, currentChar.toAscii()) );
303 currentIndex += 1;
304 continue;
306 default:
310 blockData->tokens.push_back( Token(tokenType, currentIndex, lenghtOfMatch) );
312 currentIndex += lenghtOfMatch;
314 } while (currentIndex < text.size());
317 void SyntaxHighlighter::highlightBlockInString(const QString& text, int& currentIndex, int& currentState)
319 const QRegExp &expr = mGlobals->mInStringRegexp;
320 int matchIndex = expr.indexIn(text, currentIndex);
321 if (matchIndex == -1)
322 assert(false);
324 int matchLength = expr.matchedLength();
325 setFormat(currentIndex, matchLength, mGlobals->format(StringFormat));
326 currentIndex += matchLength;
327 if (currentIndex == text.size()) {
328 // end of block
329 currentState = inString;
330 return;
333 static const QChar endOfString('\"');
334 if (text[currentIndex] == endOfString)
335 currentState = inCode;
337 setFormat(currentIndex, 1, mGlobals->format(StringFormat));
338 ++currentIndex;
339 return;
342 void SyntaxHighlighter::highlightBlockInSymbol(const QString& text, int& currentIndex, int& currentState)
344 const QRegExp &expr = mGlobals->mInSymbolRegexp;
345 int matchIndex = expr.indexIn(text, currentIndex);
346 if (matchIndex == -1)
347 assert(false);
349 int matchLength = expr.matchedLength();
350 setFormat(currentIndex, matchLength, mGlobals->format(SymbolFormat));
351 currentIndex += matchLength;
352 if (currentIndex == text.size()) {
353 // end of block
354 currentState = inSymbol;
355 return;
358 static const QChar endOfSymbol('\'');
359 if (text[currentIndex] == endOfSymbol)
360 currentState = inCode;
362 setFormat(currentIndex, 1, mGlobals->format(SymbolFormat));
363 ++currentIndex;
364 return;
367 void SyntaxHighlighter::highlightBlockInComment(const QString& text, int& currentIndex, int& currentState)
369 int index = currentIndex;
370 int maxIndex = text.size() - 1;
372 static const QString commentStart("/*");
373 static const QString commentEnd("*/");
375 int commentStartIndex = -2;
376 int commentEndIndex = -2;
378 while(index < maxIndex) {
379 if ((commentStartIndex == -2) || (commentStartIndex < index))
380 if (commentStartIndex != -1)
381 commentStartIndex = text.indexOf(commentStart, index);
383 if ((commentEndIndex == -2) || (commentEndIndex < index))
384 if (commentEndIndex != -1)
385 commentEndIndex = text.indexOf(commentEnd, index);
387 if (commentStartIndex == -1) {
388 if (commentEndIndex == -1) {
389 index = maxIndex;
390 } else {
391 index = commentEndIndex + 2;
392 --currentState;
394 } else {
395 if (commentEndIndex == -1) {
396 index = commentStartIndex + 2;
397 ++currentState;
398 } else {
399 if (commentStartIndex < commentEndIndex) {
400 index = commentStartIndex + 2;
401 ++currentState;
402 } else {
403 index = commentEndIndex + 2;
404 --currentState;
408 if (currentState < inComment) {
409 currentState = inCode;
410 break;
414 if(currentState == inCode) {
415 setFormat(currentIndex, index - currentIndex, mGlobals->format(CommentFormat));
416 currentIndex = index;
418 else {
419 setFormat(currentIndex, text.size() - currentIndex, mGlobals->format(CommentFormat));
420 currentIndex = text.size();
423 return;
426 void SyntaxHighlighter::highlightBlock(const QString& text)
428 int currentIndex = 0;
430 int currentState = previousBlockState();
431 if (currentState == -1)
432 currentState = 0;
434 TextBlockData *blockData = static_cast<TextBlockData*>(currentBlockUserData());
435 if(!blockData) {
436 blockData = new TextBlockData;
437 blockData->tokens.reserve(8);
438 setCurrentBlockUserData(blockData);
440 else {
441 blockData->tokens.clear();
444 while (currentIndex < text.size()) {
445 switch (currentState) {
446 case inCode:
447 highlightBlockInCode(text, currentIndex, currentState);
448 break;
450 case inString:
451 highlightBlockInString(text, currentIndex, currentState);
452 break;
454 case inSymbol:
455 highlightBlockInSymbol(text, currentIndex, currentState);
456 break;
458 default:
459 if(currentState >= inComment)
460 highlightBlockInComment(text, currentIndex, currentState);
463 setCurrentBlockState(currentState);