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
24 #include "highlighter.hpp"
25 #include "../../core/main.hpp"
26 #include "../../core/settings/manager.hpp"
28 #include <QApplication>
32 SyntaxHighlighterGlobals
* SyntaxHighlighterGlobals::mInstance
= 0;
34 SyntaxHighlighterGlobals::SyntaxHighlighterGlobals( Main
*main
, Settings::Manager
* settings
):
37 Q_ASSERT(mInstance
== 0);
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()
53 The highlighting algorithm demands that all regexps
54 start with a caret "^", to only match at beginning of string.
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+" );
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
;
109 QString keywordPattern
= QString("^\\b(%1)\\b").arg(keywords
.join("|"));
110 mInCodeRules
<< SyntaxRule(Token::Keyword
, keywordPattern
);
113 void SyntaxHighlighterGlobals::initBuiltins()
115 QStringList builtins
;
125 << "currentEnvironment"
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();
184 lengthOfMatch
= matchType
== Token::Unknown
? 0 : matchLength
;
188 void SyntaxHighlighter::highlightBlockInCode(const QString
& text
, int & currentIndex
, int & currentState
)
190 TextBlockData
*blockData
= static_cast<TextBlockData
*>(currentBlockUserData());
193 const QTextCharFormat
* formats
= mGlobals
->formats();
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
) );
211 if (currentChar
== symbolStart
) {
212 currentState
= inSymbol
;
213 setFormat(currentIndex
, 1, formats
[SymbolFormat
]);
214 blockData
->tokens
.push_back( Token(Token::Symbol
, currentIndex
) );
219 if (openingBrackets
.contains(currentChar
)) {
220 blockData
->tokens
.push_back(
221 Token( Token::OpeningBracket
,
223 currentChar
.toAscii() ) );
228 if (closingBrackets
.contains(currentChar
)) {
229 blockData
->tokens
.push_back(
230 Token( Token::ClosingBracket
,
232 currentChar
.toAscii() ) );
238 Token::Type tokenType
= findMatchingRule(text
, currentIndex
, lenghtOfMatch
);
242 case Token::WhiteSpace
:
243 // avoid inserting a token
244 currentIndex
+= lenghtOfMatch
;
248 setFormat(currentIndex
, lenghtOfMatch
, formats
[ClassFormat
]);
252 setFormat(currentIndex
, lenghtOfMatch
, formats
[BuiltinFormat
]);
255 case Token::Primitive
:
256 setFormat(currentIndex
, lenghtOfMatch
, formats
[PrimitiveFormat
]);
260 setFormat(currentIndex
, lenghtOfMatch
, formats
[KeywordFormat
]);
264 setFormat(currentIndex
, lenghtOfMatch
, formats
[SymbolFormat
]);
267 case Token::SymbolArg
:
268 // Omit the trailing ":" that was included in the regexp:
269 setFormat(currentIndex
, lenghtOfMatch
-1, formats
[SymbolFormat
]);
273 setFormat(currentIndex
, lenghtOfMatch
, formats
[EnvVarFormat
]);
277 setFormat(currentIndex
, lenghtOfMatch
, formats
[StringFormat
]);
281 setFormat(currentIndex
, lenghtOfMatch
, formats
[CharFormat
]);
286 case Token::RadixFloat
:
287 setFormat(currentIndex
, lenghtOfMatch
, formats
[NumberFormat
]);
290 case Token::SingleLineComment
:
291 setFormat(currentIndex
, lenghtOfMatch
, formats
[CommentFormat
]);
292 currentIndex
+= lenghtOfMatch
;
295 case Token::MultiLineCommentStart
:
296 setFormat(currentIndex
, lenghtOfMatch
, formats
[CommentFormat
]);
297 currentIndex
+= lenghtOfMatch
;
298 currentState
= inComment
;
302 blockData
->tokens
.push_back( Token(Token::Unknown
, currentIndex
, 1, currentChar
.toAscii()) );
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)
324 int matchLength
= expr
.matchedLength();
325 setFormat(currentIndex
, matchLength
, mGlobals
->format(StringFormat
));
326 currentIndex
+= matchLength
;
327 if (currentIndex
== text
.size()) {
329 currentState
= inString
;
333 static const QChar
endOfString('\"');
334 if (text
[currentIndex
] == endOfString
)
335 currentState
= inCode
;
337 setFormat(currentIndex
, 1, mGlobals
->format(StringFormat
));
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)
349 int matchLength
= expr
.matchedLength();
350 setFormat(currentIndex
, matchLength
, mGlobals
->format(SymbolFormat
));
351 currentIndex
+= matchLength
;
352 if (currentIndex
== text
.size()) {
354 currentState
= inSymbol
;
358 static const QChar
endOfSymbol('\'');
359 if (text
[currentIndex
] == endOfSymbol
)
360 currentState
= inCode
;
362 setFormat(currentIndex
, 1, mGlobals
->format(SymbolFormat
));
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) {
391 index
= commentEndIndex
+ 2;
395 if (commentEndIndex
== -1) {
396 index
= commentStartIndex
+ 2;
399 if (commentStartIndex
< commentEndIndex
) {
400 index
= commentStartIndex
+ 2;
403 index
= commentEndIndex
+ 2;
408 if (currentState
< inComment
) {
409 currentState
= inCode
;
414 if(currentState
== inCode
) {
415 setFormat(currentIndex
, index
- currentIndex
, mGlobals
->format(CommentFormat
));
416 currentIndex
= index
;
419 setFormat(currentIndex
, text
.size() - currentIndex
, mGlobals
->format(CommentFormat
));
420 currentIndex
= text
.size();
426 void SyntaxHighlighter::highlightBlock(const QString
& text
)
428 int currentIndex
= 0;
430 int currentState
= previousBlockState();
431 if (currentState
== -1)
434 TextBlockData
*blockData
= static_cast<TextBlockData
*>(currentBlockUserData());
436 blockData
= new TextBlockData
;
437 blockData
->tokens
.reserve(8);
438 setCurrentBlockUserData(blockData
);
441 blockData
->tokens
.clear();
444 while (currentIndex
< text
.size()) {
445 switch (currentState
) {
447 highlightBlockInCode(text
, currentIndex
, currentState
);
451 highlightBlockInString(text
, currentIndex
, currentState
);
455 highlightBlockInSymbol(text
, currentIndex
, currentState
);
459 if(currentState
>= inComment
)
460 highlightBlockInComment(text
, currentIndex
, currentState
);
463 setCurrentBlockState(currentState
);