/src/plugins/qmljseditor/qmljshighlighter.cpp

https://github.com/KDAB/KDAB-Creator · C++ · 334 lines · 254 code · 46 blank · 34 comment · 193 complexity · 1ff03ee02ae5eb5e8843632bbff96afb MD5 · raw file

  1. /**************************************************************************
  2. **
  3. ** This file is part of Qt Creator
  4. **
  5. ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
  6. **
  7. ** Contact: Nokia Corporation (qt-info@nokia.com)
  8. **
  9. **
  10. ** GNU Lesser General Public License Usage
  11. **
  12. ** This file may be used under the terms of the GNU Lesser General Public
  13. ** License version 2.1 as published by the Free Software Foundation and
  14. ** appearing in the file LICENSE.LGPL included in the packaging of this file.
  15. ** Please review the following information to ensure the GNU Lesser General
  16. ** Public License version 2.1 requirements will be met:
  17. ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
  18. **
  19. ** In addition, as a special exception, Nokia gives you certain additional
  20. ** rights. These rights are described in the Nokia Qt LGPL Exception
  21. ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
  22. **
  23. ** Other Usage
  24. **
  25. ** Alternatively, this file may be used in accordance with the terms and
  26. ** conditions contained in a signed written agreement between you and Nokia.
  27. **
  28. ** If you have questions regarding the use of this file, please contact
  29. ** Nokia at qt-info@nokia.com.
  30. **
  31. **************************************************************************/
  32. #include "qmljshighlighter.h"
  33. #include <QSet>
  34. #include <QtAlgorithms>
  35. #include <QDebug>
  36. #include <utils/qtcassert.h>
  37. using namespace QmlJSEditor;
  38. using namespace QmlJS;
  39. Highlighter::Highlighter(QTextDocument *parent)
  40. : TextEditor::SyntaxHighlighter(parent),
  41. m_qmlEnabled(true),
  42. m_inMultilineComment(false)
  43. {
  44. m_currentBlockParentheses.reserve(20);
  45. m_braceDepth = 0;
  46. m_foldingIndent = 0;
  47. }
  48. Highlighter::~Highlighter()
  49. {
  50. }
  51. bool Highlighter::isQmlEnabled() const
  52. {
  53. return m_qmlEnabled;
  54. }
  55. void Highlighter::setQmlEnabled(bool qmlEnabled)
  56. {
  57. m_qmlEnabled = qmlEnabled;
  58. }
  59. void Highlighter::setFormats(const QVector<QTextCharFormat> &formats)
  60. {
  61. QTC_ASSERT(formats.size() == NumFormats, return);
  62. qCopy(formats.begin(), formats.end(), m_formats);
  63. }
  64. void Highlighter::highlightBlock(const QString &text)
  65. {
  66. const QList<Token> tokens = m_scanner(text, onBlockStart());
  67. int index = 0;
  68. while (index < tokens.size()) {
  69. const Token &token = tokens.at(index);
  70. switch (token.kind) {
  71. case Token::Keyword:
  72. setFormat(token.offset, token.length, m_formats[KeywordFormat]);
  73. break;
  74. case Token::String:
  75. setFormat(token.offset, token.length, m_formats[StringFormat]);
  76. break;
  77. case Token::Comment:
  78. if (m_inMultilineComment && text.midRef(token.end() - 2, 2) == QLatin1String("*/")) {
  79. onClosingParenthesis('-', token.end() - 1, index == tokens.size()-1);
  80. m_inMultilineComment = false;
  81. } else if (!m_inMultilineComment
  82. && (m_scanner.state() & Scanner::MultiLineMask) == Scanner::MultiLineComment
  83. && index == tokens.size() - 1) {
  84. onOpeningParenthesis('+', token.offset, index == 0);
  85. m_inMultilineComment = true;
  86. }
  87. setFormat(token.offset, token.length, m_formats[CommentFormat]);
  88. break;
  89. case Token::RegExp:
  90. setFormat(token.offset, token.length, m_formats[StringFormat]);
  91. break;
  92. case Token::LeftParenthesis:
  93. onOpeningParenthesis('(', token.offset, index == 0);
  94. break;
  95. case Token::RightParenthesis:
  96. onClosingParenthesis(')', token.offset, index == tokens.size()-1);
  97. break;
  98. case Token::LeftBrace:
  99. onOpeningParenthesis('{', token.offset, index == 0);
  100. break;
  101. case Token::RightBrace:
  102. onClosingParenthesis('}', token.offset, index == tokens.size()-1);
  103. break;
  104. case Token::LeftBracket:
  105. onOpeningParenthesis('[', token.offset, index == 0);
  106. break;
  107. case Token::RightBracket:
  108. onClosingParenthesis(']', token.offset, index == tokens.size()-1);
  109. break;
  110. case Token::Identifier: {
  111. if (!m_qmlEnabled)
  112. break;
  113. const QStringRef spell = text.midRef(token.offset, token.length);
  114. if (maybeQmlKeyword(spell)) {
  115. // check the previous token
  116. if (index == 0 || tokens.at(index - 1).isNot(Token::Dot)) {
  117. if (index + 1 == tokens.size() || tokens.at(index + 1).isNot(Token::Colon)) {
  118. setFormat(token.offset, token.length, m_formats[KeywordFormat]);
  119. break;
  120. }
  121. }
  122. } else if (index > 0 && maybeQmlBuiltinType(spell)) {
  123. const Token &previousToken = tokens.at(index - 1);
  124. if (previousToken.is(Token::Identifier) && text.at(previousToken.offset) == QLatin1Char('p')
  125. && text.midRef(previousToken.offset, previousToken.length) == QLatin1String("property")) {
  126. setFormat(token.offset, token.length, m_formats[KeywordFormat]);
  127. break;
  128. }
  129. }
  130. } break;
  131. case Token::Delimiter:
  132. break;
  133. default:
  134. break;
  135. } // end swtich
  136. ++index;
  137. }
  138. int previousTokenEnd = 0;
  139. for (int index = 0; index < tokens.size(); ++index) {
  140. const Token &token = tokens.at(index);
  141. setFormat(previousTokenEnd, token.begin() - previousTokenEnd, m_formats[VisualWhitespace]);
  142. switch (token.kind) {
  143. case Token::Comment:
  144. case Token::String:
  145. case Token::RegExp: {
  146. int i = token.begin(), e = token.end();
  147. while (i < e) {
  148. const QChar ch = text.at(i);
  149. if (ch.isSpace()) {
  150. const int start = i;
  151. do {
  152. ++i;
  153. } while (i < e && text.at(i).isSpace());
  154. setFormat(start, i - start, m_formats[VisualWhitespace]);
  155. } else {
  156. ++i;
  157. }
  158. }
  159. } break;
  160. default:
  161. break;
  162. } // end of switch
  163. previousTokenEnd = token.end();
  164. }
  165. setFormat(previousTokenEnd, text.length() - previousTokenEnd, m_formats[VisualWhitespace]);
  166. setCurrentBlockState(m_scanner.state());
  167. onBlockEnd(m_scanner.state());
  168. }
  169. bool Highlighter::maybeQmlKeyword(const QStringRef &text) const
  170. {
  171. if (text.isEmpty())
  172. return false;
  173. const QChar ch = text.at(0);
  174. if (ch == QLatin1Char('p') && text == QLatin1String("property")) {
  175. return true;
  176. } else if (ch == QLatin1Char('a') && text == QLatin1String("alias")) {
  177. return true;
  178. } else if (ch == QLatin1Char('s') && text == QLatin1String("signal")) {
  179. return true;
  180. } else if (ch == QLatin1Char('p') && text == QLatin1String("property")) {
  181. return true;
  182. } else if (ch == QLatin1Char('r') && text == QLatin1String("readonly")) {
  183. return true;
  184. } else if (ch == QLatin1Char('i') && text == QLatin1String("import")) {
  185. return true;
  186. } else if (ch == QLatin1Char('o') && text == QLatin1String("on")) {
  187. return true;
  188. } else {
  189. return false;
  190. }
  191. }
  192. bool Highlighter::maybeQmlBuiltinType(const QStringRef &text) const
  193. {
  194. if (text.isEmpty())
  195. return false;
  196. const QChar ch = text.at(0);
  197. if (ch == QLatin1Char('a') && text == QLatin1String("action")) {
  198. return true;
  199. } else if (ch == QLatin1Char('b') && text == QLatin1String("bool")) {
  200. return true;
  201. } else if (ch == QLatin1Char('c') && text == QLatin1String("color")) {
  202. return true;
  203. } else if (ch == QLatin1Char('d') && text == QLatin1String("date")) {
  204. return true;
  205. } else if (ch == QLatin1Char('d') && text == QLatin1String("double")) {
  206. return true;
  207. } else if (ch == QLatin1Char('e') && text == QLatin1String("enumeration")) {
  208. return true;
  209. } else if (ch == QLatin1Char('f') && text == QLatin1String("font")) {
  210. return true;
  211. } else if (ch == QLatin1Char('i') && text == QLatin1String("int")) {
  212. return true;
  213. } else if (ch == QLatin1Char('l') && text == QLatin1String("list")) {
  214. return true;
  215. } else if (ch == QLatin1Char('p') && text == QLatin1String("point")) {
  216. return true;
  217. } else if (ch == QLatin1Char('r') && text == QLatin1String("real")) {
  218. return true;
  219. } else if (ch == QLatin1Char('r') && text == QLatin1String("rect")) {
  220. return true;
  221. } else if (ch == QLatin1Char('s') && text == QLatin1String("size")) {
  222. return true;
  223. } else if (ch == QLatin1Char('s') && text == QLatin1String("string")) {
  224. return true;
  225. } else if (ch == QLatin1Char('t') && text == QLatin1String("time")) {
  226. return true;
  227. } else if (ch == QLatin1Char('u') && text == QLatin1String("url")) {
  228. return true;
  229. } else if (ch == QLatin1Char('v') && text == QLatin1String("variant")) {
  230. return true;
  231. } else if (ch == QLatin1Char('v') && text == QLatin1String("var")) {
  232. return true;
  233. } else if (ch == QLatin1Char('v') && text == QLatin1String("vector3d")) {
  234. return true;
  235. } else {
  236. return false;
  237. }
  238. }
  239. int Highlighter::onBlockStart()
  240. {
  241. m_currentBlockParentheses.clear();
  242. m_braceDepth = 0;
  243. m_foldingIndent = 0;
  244. m_inMultilineComment = false;
  245. if (TextEditor::TextBlockUserData *userData = TextEditor::BaseTextDocumentLayout::testUserData(currentBlock())) {
  246. userData->setFoldingIndent(0);
  247. userData->setFoldingStartIncluded(false);
  248. userData->setFoldingEndIncluded(false);
  249. }
  250. int state = 0;
  251. int previousState = previousBlockState();
  252. if (previousState != -1) {
  253. state = previousState & 0xff;
  254. m_braceDepth = (previousState >> 8);
  255. m_inMultilineComment = ((state & Scanner::MultiLineMask) == Scanner::MultiLineComment);
  256. }
  257. m_foldingIndent = m_braceDepth;
  258. return state;
  259. }
  260. void Highlighter::onBlockEnd(int state)
  261. {
  262. typedef TextEditor::TextBlockUserData TextEditorBlockData;
  263. setCurrentBlockState((m_braceDepth << 8) | state);
  264. TextEditor::BaseTextDocumentLayout::setParentheses(currentBlock(), m_currentBlockParentheses);
  265. TextEditor::BaseTextDocumentLayout::setFoldingIndent(currentBlock(), m_foldingIndent);
  266. }
  267. void Highlighter::onOpeningParenthesis(QChar parenthesis, int pos, bool atStart)
  268. {
  269. if (parenthesis == QLatin1Char('{') || parenthesis == QLatin1Char('[') || parenthesis == QLatin1Char('+')) {
  270. ++m_braceDepth;
  271. // if a folding block opens at the beginning of a line, treat the entire line
  272. // as if it were inside the folding block
  273. if (atStart)
  274. TextEditor::BaseTextDocumentLayout::userData(currentBlock())->setFoldingStartIncluded(true);
  275. }
  276. m_currentBlockParentheses.push_back(Parenthesis(Parenthesis::Opened, parenthesis, pos));
  277. }
  278. void Highlighter::onClosingParenthesis(QChar parenthesis, int pos, bool atEnd)
  279. {
  280. if (parenthesis == QLatin1Char('}') || parenthesis == QLatin1Char(']') || parenthesis == QLatin1Char('-')) {
  281. --m_braceDepth;
  282. if (atEnd)
  283. TextEditor::BaseTextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
  284. else
  285. m_foldingIndent = qMin(m_braceDepth, m_foldingIndent); // folding indent is the minimum brace depth of a block
  286. }
  287. m_currentBlockParentheses.push_back(Parenthesis(Parenthesis::Closed, parenthesis, pos));
  288. }