LUAEditorSyntaxHighlighter.cpp 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "LUAEditorSyntaxHighlighter.hxx"
  9. #include <Source/LUA/moc_LUAEditorSyntaxHighlighter.cpp>
  10. #include "LUAEditorStyleMessages.h"
  11. #include "LUAEditorBlockState.h"
  12. namespace LUAEditor
  13. {
  14. namespace
  15. {
  16. template<typename Container>
  17. void CreateTypes([[maybe_unused]] Container& container)
  18. {
  19. }
  20. template<typename Container, typename Type, typename ... Types>
  21. void CreateTypes(Container& container)
  22. {
  23. container.push_back(azcreate(Type, ()));
  24. CreateTypes<Container, Types...>(container);
  25. }
  26. enum class ParserStates : int
  27. {
  28. Null,
  29. Name,
  30. ShortComment,
  31. LongComment,
  32. Number,
  33. NumberHex,
  34. StringLiteral,
  35. NumStates
  36. };
  37. class BaseParserState
  38. {
  39. public:
  40. virtual ~BaseParserState() {}
  41. virtual bool IsMultilineState([[maybe_unused]] LUASyntaxHighlighter::StateMachine& machine) const { return false; }
  42. virtual void StartState([[maybe_unused]] LUASyntaxHighlighter::StateMachine& machine) {}
  43. //note you only get 13 bits of usable space here. see QTBlockState m_syntaxHighlighterStateExtra
  44. virtual AZ::u16 GetSaveState() const { return 0; }
  45. virtual void SetSaveState([[maybe_unused]] AZ::u16 state) {}
  46. virtual void Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar) = 0;
  47. };
  48. class NullParserState
  49. : public BaseParserState
  50. {
  51. void Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar) override;
  52. };
  53. class NameParserState
  54. : public BaseParserState
  55. {
  56. void Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar) override;
  57. };
  58. class ShortCommentParserState
  59. : public BaseParserState
  60. {
  61. void StartState(LUASyntaxHighlighter::StateMachine& machine) override;
  62. void Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar) override;
  63. bool m_mightBeLong;
  64. };
  65. class NumberParserState
  66. : public BaseParserState
  67. {
  68. void Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar) override;
  69. };
  70. class NumberHexParserState
  71. : public BaseParserState
  72. {
  73. void Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar) override;
  74. };
  75. class LongCommentParserState
  76. : public BaseParserState
  77. {
  78. bool IsMultilineState([[maybe_unused]] LUASyntaxHighlighter::StateMachine& machine) const override { return true; }
  79. void StartState(LUASyntaxHighlighter::StateMachine& machine) override;
  80. AZ::u16 GetSaveState() const override { return m_bracketLevel; }
  81. void SetSaveState(AZ::u16 state) override;
  82. void Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar) override;
  83. AZ::u16 m_bracketLevel;
  84. QString m_bracketEnd;
  85. bool m_endNextChar;
  86. };
  87. class StringLiteralParserState
  88. : public BaseParserState
  89. {
  90. bool IsMultilineState(LUASyntaxHighlighter::StateMachine& machine) const override;
  91. void StartState(LUASyntaxHighlighter::StateMachine& machine) override;
  92. AZ::u16 GetSaveState() const override { return m_bracketLevel; }
  93. void SetSaveState(AZ::u16 state) override;
  94. void Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar) override;
  95. AZ::u16 m_bracketLevel; //if 0 string started with a ' if 1 started with a " if 2 or more started with an long braket of level m_BracketLevel-2
  96. bool m_endNextChar;
  97. QString m_bracketEnd;
  98. bool m_mightBeLong;
  99. };
  100. }
  101. // GCC triggers a subobject linkage warning if a symbol in an anonymous namespace in included in another file
  102. // When unity files are enabled in CMake, this file is included in a .cxx file which triggers this warning
  103. // The code is actually OK as this file is a translation unit and not a header file where this warning would normally be triggered
  104. AZ_PUSH_DISABLE_WARNING_GCC("-Wsubobject-linkage");
  105. class LUASyntaxHighlighter::StateMachine
  106. {
  107. AZ_POP_DISABLE_WARNING_GCC
  108. public:
  109. StateMachine();
  110. ~StateMachine();
  111. void Parse(const QString& text);
  112. //extraBack is if you want to include previous chars as part as the current string after the state change
  113. void SetState(ParserStates state, int extraBack = 0);
  114. void PassState(ParserStates state); //change state but keep data captured so far, use if we are in "wrong" state
  115. void Reset();
  116. int CurrentLength() const { return m_currentChar - m_start + 1; }
  117. QStringRef CurrentString() const { return QStringRef(m_currentString, m_start, CurrentLength()); }
  118. const QString* GetFullLine() const { return m_currentString; }
  119. QTBlockState GetSaveState() const;
  120. void SetSaveState(QTBlockState state);
  121. void IncFoldLevel()
  122. {
  123. ++m_foldLevel;
  124. if (m_onIncFoldLevel)
  125. {
  126. m_onIncFoldLevel(m_foldLevel);
  127. }
  128. }
  129. void DecFoldLevel()
  130. {
  131. if (m_foldLevel > 0)
  132. {
  133. --m_foldLevel;
  134. }
  135. if (m_onDecFoldLevel)
  136. {
  137. m_onDecFoldLevel(m_foldLevel);
  138. }
  139. }
  140. ParserStates GetCurrentParserState() const { return m_currentState; }
  141. bool IsJoiningNames() const { return m_joinNames; }
  142. void SetJoiningNames(bool joinNames) { m_joinNames = joinNames; }
  143. template<typename T>
  144. void SetOnIncFoldLevel(const T& callable) { m_onIncFoldLevel = callable; }
  145. template<typename T>
  146. void SetOnDecFoldLevel(const T& callable) { m_onDecFoldLevel = callable; }
  147. AZStd::function<void(ParserStates state, int position, int length)> CaptureToken;
  148. private:
  149. BaseParserState* GetCurrentState() const { return m_states[static_cast<int>(m_currentState)]; }
  150. using StatesListType = AZStd::fixed_vector<BaseParserState*, static_cast<int>(ParserStates::NumStates)>;
  151. StatesListType m_states;
  152. ParserStates m_currentState{ ParserStates::Null };
  153. int m_currentChar;
  154. int m_start;
  155. const QString* m_currentString;
  156. int m_foldLevel;
  157. bool m_joinNames{ true }; //consider names seperated by . and : as one for highlighting purposes
  158. AZStd::function<void(int)> m_onIncFoldLevel;
  159. AZStd::function<void(int)> m_onDecFoldLevel;
  160. };
  161. LUASyntaxHighlighter::StateMachine::StateMachine()
  162. {
  163. CreateTypes<StatesListType, NullParserState, NameParserState, ShortCommentParserState,
  164. LongCommentParserState, NumberParserState, NumberHexParserState, StringLiteralParserState>(m_states);
  165. Reset();
  166. }
  167. LUASyntaxHighlighter::StateMachine::~StateMachine()
  168. {
  169. AZStd::for_each(m_states.begin(), m_states.end(), [](BaseParserState* state) { azdestroy(state); });
  170. }
  171. void LUASyntaxHighlighter::StateMachine::Reset()
  172. {
  173. m_start = 0;
  174. m_currentChar = -1;
  175. m_currentState = ParserStates::Null;
  176. m_currentString = nullptr;
  177. m_foldLevel = 0;
  178. }
  179. void LUASyntaxHighlighter::StateMachine::Parse(const QString& text)
  180. {
  181. m_currentString = &text;
  182. for (m_currentChar = 0; m_currentChar != text.length(); ++m_currentChar)
  183. {
  184. GetCurrentState()->Parse(*this, text[m_currentChar]);
  185. }
  186. //we only highlight on line at most at a time. so if this is a multiline highlight, go ahead an highlight this line as part of that now.
  187. if (GetCurrentState()->IsMultilineState(*this))
  188. {
  189. if (CaptureToken && m_start != m_currentChar)
  190. {
  191. CaptureToken(m_currentState, m_start, m_currentChar - m_start);
  192. }
  193. }
  194. else
  195. {
  196. if (m_currentState != ParserStates::Null)
  197. {
  198. SetState(ParserStates::Null);
  199. }
  200. else
  201. {
  202. if (CaptureToken && m_start != m_currentChar)
  203. {
  204. CaptureToken(m_currentState, m_start, m_currentChar - m_start);
  205. }
  206. }
  207. }
  208. }
  209. void LUASyntaxHighlighter::StateMachine::SetState(ParserStates state, int extraBack)
  210. {
  211. if (m_currentState != state)
  212. {
  213. int currentChar = m_currentChar - extraBack;
  214. if (CaptureToken && m_start < currentChar)
  215. {
  216. CaptureToken(m_currentState, m_start, currentChar - m_start);
  217. }
  218. m_currentState = state;
  219. m_start = currentChar;
  220. GetCurrentState()->StartState(*this);
  221. //if going back to null state, see if this char might be the start of a new capture.
  222. if (m_currentState == ParserStates::Null && m_start < m_currentString->length())
  223. {
  224. GetCurrentState()->Parse(*this, m_currentString->at(m_start));
  225. }
  226. }
  227. }
  228. void LUASyntaxHighlighter::StateMachine::PassState(ParserStates state)
  229. {
  230. m_currentState = state;
  231. GetCurrentState()->StartState(*this);
  232. //if going back to null state, see if this char might be the start of a new capture.
  233. if (m_currentState == ParserStates::Null && m_start < m_currentString->length())
  234. {
  235. GetCurrentState()->Parse(*this, m_currentString->at(m_currentChar));
  236. }
  237. }
  238. ///1st bit to detect uninitialized blocks, next 14 bits store folding depth, next 3 bits store state machine state, final 14 bits store state specific user data.
  239. QTBlockState LUASyntaxHighlighter::StateMachine::GetSaveState() const
  240. {
  241. QTBlockState result;
  242. result.m_blockState.m_uninitialized = 0;
  243. result.m_blockState.m_folded = 0;
  244. result.m_blockState.m_foldLevel = m_foldLevel;
  245. result.m_blockState.m_syntaxHighlighterState = static_cast<int>(m_currentState);
  246. result.m_blockState.m_syntaxHighlighterStateExtra = GetCurrentState()->GetSaveState();
  247. return result;
  248. }
  249. void LUASyntaxHighlighter::StateMachine::SetSaveState(QTBlockState state)
  250. {
  251. static_assert(static_cast<int>(ParserStates::NumStates) <= 8, "We are only using 3 bits for state in lua parser currently");
  252. Reset();
  253. if (!state.m_blockState.m_uninitialized)
  254. {
  255. m_currentState = static_cast<ParserStates>(state.m_blockState.m_syntaxHighlighterState);
  256. GetCurrentState()->SetSaveState(state.m_blockState.m_syntaxHighlighterStateExtra);
  257. m_foldLevel = state.m_blockState.m_foldLevel;
  258. }
  259. }
  260. void NullParserState::Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar)
  261. {
  262. if (nextChar.isLetter() || nextChar == '_')
  263. {
  264. machine.SetState(ParserStates::Name);
  265. }
  266. else if (nextChar.isNumber() || nextChar == '-' || nextChar == '+')
  267. {
  268. machine.SetState(ParserStates::Number);
  269. }
  270. else if (nextChar == '\'' || nextChar == '"' || nextChar == '[')
  271. {
  272. machine.SetState(ParserStates::StringLiteral);
  273. }
  274. else if (nextChar == '{')
  275. {
  276. machine.IncFoldLevel();
  277. }
  278. else if (nextChar == '}')
  279. {
  280. machine.DecFoldLevel();
  281. }
  282. }
  283. void NameParserState::Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar)
  284. {
  285. if (!nextChar.isLetterOrNumber() && nextChar != '_' && (!machine.IsJoiningNames() || (nextChar != '.' && nextChar != ':')))
  286. {
  287. machine.SetState(ParserStates::Null);
  288. }
  289. }
  290. void ShortCommentParserState::Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar)
  291. {
  292. if (machine.CurrentLength() == 3 && nextChar != '[')
  293. {
  294. m_mightBeLong = false;
  295. }
  296. else if (machine.CurrentLength() >= 4 && machine.CurrentLength() < USHRT_MAX && m_mightBeLong) //we cant catch longer than 16k level comments.
  297. {
  298. if (nextChar != '=' && nextChar != '[')
  299. {
  300. m_mightBeLong = false;
  301. }
  302. else if (nextChar == '[')
  303. {
  304. machine.PassState(ParserStates::LongComment);
  305. }
  306. }
  307. }
  308. void ShortCommentParserState::StartState([[maybe_unused]] LUASyntaxHighlighter::StateMachine& machine)
  309. {
  310. m_mightBeLong = true;
  311. }
  312. void LongCommentParserState::StartState(LUASyntaxHighlighter::StateMachine& machine)
  313. {
  314. auto token = machine.CurrentString();
  315. int start = token.indexOf('[');
  316. AZ_Assert(start != -1, "Shouldn't have been able to get to this state without opening long bracket");
  317. int finish = token.indexOf('[', start + 1);
  318. AZ_Assert(finish != -1 && finish != start, "Shouldn't have been able to get to this state without opening long bracket");
  319. m_bracketLevel = static_cast<AZ::u16>(finish - start - 1);
  320. m_bracketEnd = "]";
  321. for (int i = 0; i < m_bracketLevel; ++i)
  322. {
  323. m_bracketEnd.append('=');
  324. }
  325. m_bracketEnd.append(']');
  326. m_endNextChar = false;
  327. }
  328. void LongCommentParserState::Parse(LUASyntaxHighlighter::StateMachine& machine, [[maybe_unused]] const QChar& nextChar)
  329. {
  330. if (m_endNextChar)
  331. {
  332. machine.SetState(ParserStates::Null);
  333. return;
  334. }
  335. QStringRef token = machine.CurrentString();
  336. if (token.endsWith(m_bracketEnd))
  337. {
  338. if (machine.GetFullLine()->size() >= token.size())
  339. {
  340. machine.SetState(ParserStates::Null, -1);
  341. }
  342. else
  343. {
  344. m_endNextChar = true;
  345. }
  346. }
  347. }
  348. void LongCommentParserState::SetSaveState(AZ::u16 state)
  349. {
  350. m_bracketLevel = state;
  351. m_bracketEnd = "]";
  352. for (int i = 0; i < m_bracketLevel; ++i)
  353. {
  354. m_bracketEnd.append('=');
  355. }
  356. m_bracketEnd.append(']');
  357. m_endNextChar = false;
  358. }
  359. void NumberParserState::Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar)
  360. {
  361. auto currentString = machine.CurrentString();
  362. if (currentString.endsWith("--"))
  363. {
  364. machine.SetState(ParserStates::ShortComment, 1);
  365. return;
  366. }
  367. auto lower = nextChar.toLower();
  368. if (lower == 'x')
  369. {
  370. machine.PassState(ParserStates::NumberHex);
  371. }
  372. else if (!(nextChar.isNumber() || nextChar == '.' || lower == 'e'))
  373. {
  374. if (currentString.length() == 2 && (currentString.at(0) == '+' || currentString.at(0) == '-'))
  375. {
  376. machine.PassState(ParserStates::Null);
  377. }
  378. else
  379. {
  380. machine.SetState(ParserStates::Null);
  381. }
  382. }
  383. }
  384. void NumberHexParserState::Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar)
  385. {
  386. auto lower = nextChar.toLower();
  387. if (!(nextChar.isNumber() || nextChar == '.' || lower == 'p'))
  388. {
  389. machine.SetState(ParserStates::Null);
  390. }
  391. }
  392. void StringLiteralParserState::StartState(LUASyntaxHighlighter::StateMachine& machine)
  393. {
  394. m_endNextChar = false;
  395. m_mightBeLong = false;
  396. auto currentString = machine.CurrentString();
  397. AZ_Assert(!currentString.isEmpty(), "Sting literal string shouldn't be empty")
  398. if (currentString.at(0) == '\'')
  399. {
  400. m_bracketLevel = 0;
  401. }
  402. if (currentString.at(0) == '"')
  403. {
  404. m_bracketLevel = 1;
  405. }
  406. if (currentString.at(0) == '[')
  407. {
  408. m_bracketLevel = 2;
  409. m_mightBeLong = true;
  410. m_bracketEnd = "]";
  411. }
  412. }
  413. void StringLiteralParserState::Parse(LUASyntaxHighlighter::StateMachine& machine, const QChar& nextChar)
  414. {
  415. if (m_endNextChar)
  416. {
  417. machine.SetState(ParserStates::Null);
  418. return;
  419. }
  420. if (m_mightBeLong)
  421. {
  422. if (nextChar == '[')
  423. {
  424. m_mightBeLong = false; //it actually is long, = length will already be in m_bracketLevel
  425. m_bracketEnd += ']';
  426. return;
  427. }
  428. else if (nextChar == '=')
  429. {
  430. ++m_bracketLevel;
  431. m_bracketEnd += '=';
  432. }
  433. else
  434. {
  435. machine.PassState(ParserStates::Null); //turns out we weren't actually a string literal
  436. return;
  437. }
  438. }
  439. if (m_bracketLevel == 0 && nextChar == '\'' && !machine.CurrentString().endsWith("\\'"))
  440. {
  441. m_endNextChar = true;
  442. }
  443. if (m_bracketLevel == 1 && nextChar == '"' && !machine.CurrentString().endsWith("\\\""))
  444. {
  445. m_endNextChar = true;
  446. }
  447. if (m_bracketLevel >= 2 && machine.CurrentString().endsWith(m_bracketEnd))
  448. {
  449. m_endNextChar = true;
  450. }
  451. }
  452. bool StringLiteralParserState::IsMultilineState(LUASyntaxHighlighter::StateMachine& machine) const
  453. {
  454. auto currentString = machine.GetFullLine();
  455. if ((m_bracketLevel > 1 && !m_endNextChar && !m_mightBeLong) || currentString->endsWith(QString("\\")) || currentString->endsWith(QString("\\z")))
  456. {
  457. return true;
  458. }
  459. else
  460. {
  461. return false;
  462. }
  463. }
  464. void StringLiteralParserState::SetSaveState(AZ::u16 state)
  465. {
  466. m_bracketLevel = state;
  467. if (m_bracketLevel >= 2)
  468. {
  469. m_bracketEnd = "]";
  470. for (int i = 0; i < m_bracketLevel - 2; ++i)
  471. {
  472. m_bracketEnd.append('=');
  473. }
  474. m_bracketEnd.append(']');
  475. }
  476. m_endNextChar = false;
  477. m_mightBeLong = false;
  478. }
  479. LUASyntaxHighlighter::LUASyntaxHighlighter(QWidget* parent)
  480. : QSyntaxHighlighter(parent)
  481. , m_machine(azcreate(StateMachine, ()))
  482. {
  483. AddBlockKeywords();
  484. BuildRegExes();
  485. }
  486. LUASyntaxHighlighter::LUASyntaxHighlighter(QTextDocument* parent)
  487. : QSyntaxHighlighter(parent)
  488. , m_machine(azcreate(StateMachine, ()))
  489. {
  490. AddBlockKeywords();
  491. BuildRegExes();
  492. }
  493. void LUASyntaxHighlighter::BuildRegExes()
  494. {
  495. auto colors =
  496. AZ::UserSettings::CreateFind<SyntaxStyleSettings>(AZ_CRC_CE("LUA Editor Text Settings"), AZ::UserSettings::CT_GLOBAL);
  497. // Order of declaration matters as some rules can stop the next ones from being processed
  498. // Match against ; : , . = * - + / < >
  499. {
  500. HighlightingRule rule;
  501. rule.pattern = QRegularExpression(R"([\;\:\,\.\=\*\-\+\/\<\>])");
  502. rule.colorCB = [colors]()
  503. {
  504. return colors->GetSpecialCharacterColor();
  505. };
  506. m_highlightingRules.push_back(rule);
  507. }
  508. // Match against parenthesis and brackets
  509. {
  510. HighlightingRule rule;
  511. rule.pattern = QRegularExpression(R"([\(\)\{\}\[\]])");
  512. rule.colorCB = [colors]()
  513. {
  514. return colors->GetBracketColor();
  515. };
  516. m_highlightingRules.push_back(rule);
  517. }
  518. // Match methods and definitions. Any word which ends with '('
  519. {
  520. HighlightingRule rule;
  521. rule.pattern = QRegularExpression(R"(\b[A-Za-z0-9_]+(?=\())");
  522. rule.colorCB = [colors]()
  523. {
  524. return colors->GetMethodColor();
  525. };
  526. m_highlightingRules.push_back(rule);
  527. }
  528. // Match any word which ends with ':'
  529. {
  530. HighlightingRule rule;
  531. rule.pattern = QRegularExpression(R"(\b[A-Za-z0-9_]+(?=\:))");
  532. rule.colorCB = [colors]()
  533. {
  534. return colors->GetLibraryColor();
  535. };
  536. m_highlightingRules.push_back(rule);
  537. }
  538. // Match against local, self, true, false and nil keywords
  539. {
  540. HighlightingRule rule;
  541. rule.stopProcessingMoreRulesAfterThis = true;
  542. rule.pattern = QRegularExpression(R"(\bself\b|\blocal\b|\btrue\b|\bfalse\b|\bnil\b)");
  543. rule.colorCB = [colors]()
  544. {
  545. return colors->GetSpecialKeywordColor();
  546. };
  547. m_highlightingRules.push_back(rule);
  548. }
  549. // Match against reserved keywords such as function, then, if, etc
  550. const HighlightedWords::LUAKeywordsType* keywords = nullptr;
  551. HighlightedWords::Bus::BroadcastResult(keywords, &HighlightedWords::Bus::Events::GetLUAKeywords);
  552. if (keywords)
  553. {
  554. QString pattern;
  555. for (const AZStd::string& keyword : *keywords)
  556. {
  557. pattern += "\\b";
  558. pattern += keyword.c_str();
  559. pattern += "\\b|";
  560. }
  561. pattern.chop(1); // remove last |
  562. HighlightingRule rule;
  563. rule.stopProcessingMoreRulesAfterThis = true;
  564. rule.pattern = QRegularExpression(pattern);
  565. rule.colorCB = [colors]()
  566. {
  567. return colors->GetKeywordColor();
  568. };
  569. m_highlightingRules.push_back(rule);
  570. }
  571. }
  572. void LUASyntaxHighlighter::AddBlockKeywords()
  573. {
  574. //these don't catch tables. that is handled in the null machine state.
  575. m_LUAStartBlockKeywords.clear();
  576. AZStd::string startKeywords[] = {
  577. {"do"}, {"if"}, {"function"}, {"repeat"}
  578. };
  579. m_LUAStartBlockKeywords.insert(std::begin(startKeywords), std::end(startKeywords));
  580. m_LUAEndBlockKeywords.clear();
  581. AZStd::string endKeywords[] = {
  582. {"end"}, {"until"}
  583. };
  584. m_LUAEndBlockKeywords.insert(std::begin(endKeywords), std::end(endKeywords));
  585. }
  586. LUASyntaxHighlighter::~LUASyntaxHighlighter()
  587. {
  588. azdestroy(m_machine);
  589. }
  590. void LUASyntaxHighlighter::highlightBlock(const QString& text)
  591. {
  592. m_machine->SetJoiningNames(true);
  593. m_machine->SetOnIncFoldLevel([](int) {});
  594. m_machine->SetOnDecFoldLevel([](int) {});
  595. auto colors = AZ::UserSettings::CreateFind<SyntaxStyleSettings>(AZ_CRC_CE("LUA Editor Text Settings"), AZ::UserSettings::CT_GLOBAL);
  596. const HighlightedWords::LUAKeywordsType* libraryFuncs = nullptr;
  597. HighlightedWords::Bus::BroadcastResult(libraryFuncs, &HighlightedWords::Bus::Events::GetLUALibraryFunctions);
  598. auto cBlock = currentBlock();
  599. QTBlockState currentState;
  600. currentState.m_qtBlockState = currentBlockState();
  601. QTextCharFormat textFormat;
  602. textFormat.setFont(colors->GetFont());
  603. textFormat.setForeground(colors->GetTextColor());
  604. setFormat(0, cBlock.length(), textFormat);
  605. QTextCharFormat spaceFormat = QTextCharFormat();
  606. spaceFormat.setForeground(colors->GetTextWhitespaceColor());
  607. QRegExp tabsAndSpaces("( |\t)+");
  608. int index = tabsAndSpaces.indexIn(text);
  609. while (index >= 0)
  610. {
  611. int length = tabsAndSpaces.matchedLength();
  612. setFormat(index, length, spaceFormat);
  613. index = tabsAndSpaces.indexIn(text, index + length);
  614. }
  615. QTBlockState prevState;
  616. prevState.m_qtBlockState = previousBlockState();
  617. m_machine->SetSaveState(prevState);
  618. auto startingState = m_machine->GetCurrentParserState();
  619. m_machine->CaptureToken = [&](ParserStates state, int position, int length)
  620. {
  621. if (state == ParserStates::Name)
  622. {
  623. const AZStd::string dhText(text.mid(position, length).toUtf8().constData());
  624. if (libraryFuncs && libraryFuncs->find(dhText) != libraryFuncs->end())
  625. {
  626. textFormat.setForeground(colors->GetLibraryColor());
  627. setFormat(position, length, textFormat);
  628. }
  629. else
  630. {
  631. textFormat.setForeground(colors->GetTextColor());
  632. setFormat(position, length, textFormat);
  633. }
  634. if (m_LUAStartBlockKeywords.find(dhText) != m_LUAStartBlockKeywords.end())
  635. {
  636. m_machine->IncFoldLevel();
  637. }
  638. if (m_LUAEndBlockKeywords.find(dhText) != m_LUAEndBlockKeywords.end())
  639. {
  640. m_machine->DecFoldLevel();
  641. }
  642. }
  643. if (state == ParserStates::ShortComment || state == ParserStates::LongComment)
  644. {
  645. textFormat.setForeground(colors->GetCommentColor());
  646. setFormat(position, length, textFormat);
  647. }
  648. if (state == ParserStates::Number || state == ParserStates::NumberHex)
  649. {
  650. textFormat.setForeground(colors->GetNumberColor());
  651. setFormat(position, length, textFormat);
  652. }
  653. if (state == ParserStates::StringLiteral)
  654. {
  655. textFormat.setForeground(colors->GetStringLiteralColor());
  656. setFormat(position, length, textFormat);
  657. }
  658. const bool canRunRegEx = state == ParserStates::Null || state == ParserStates::Name;
  659. if (!canRunRegEx)
  660. return;
  661. // Special case to allow to lint methods via regex
  662. const int nextCharPos = position + length;
  663. const bool nextCharNeededForRegEx = text.at(nextCharPos) == '(' || text.at(nextCharPos) == ':';
  664. const QString dhText = nextCharNeededForRegEx ? text.mid(position, length + 1) : text.mid(position, length);
  665. for (const HighlightingRule& rule : m_highlightingRules)
  666. {
  667. bool hasMatch = false;
  668. QRegularExpressionMatchIterator i = rule.pattern.globalMatch(dhText);
  669. while (i.hasNext())
  670. {
  671. hasMatch = true;
  672. QRegularExpressionMatch match = i.next();
  673. textFormat.setForeground(rule.colorCB());
  674. setFormat(position + match.capturedStart(), match.capturedLength(), textFormat);
  675. }
  676. if (hasMatch && rule.stopProcessingMoreRulesAfterThis)
  677. return;
  678. }
  679. };
  680. m_machine->Parse(text);
  681. // Selected bracket matching highlighting
  682. if (m_openBracketPos >= 0 && m_closeBracketPos >= 0)
  683. {
  684. if (cBlock.contains(m_openBracketPos))
  685. {
  686. setFormat(m_openBracketPos - cBlock.position(), 1, colors->GetSelectedBracketColor());
  687. }
  688. if (cBlock.contains(m_closeBracketPos))
  689. {
  690. setFormat(m_closeBracketPos - cBlock.position(), 1, colors->GetSelectedBracketColor());
  691. }
  692. }
  693. else if (m_openBracketPos >= 0)
  694. {
  695. if (cBlock.contains(m_openBracketPos))
  696. {
  697. setFormat(m_openBracketPos - cBlock.position(), 1, colors->GetUnmatchedBracketColor());
  698. }
  699. if (cBlock.contains(m_closeBracketPos))
  700. {
  701. setFormat(m_closeBracketPos - cBlock.position(), 1, colors->GetUnmatchedBracketColor());
  702. }
  703. }
  704. auto endingState = m_machine->GetCurrentParserState();
  705. if (startingState != ParserStates::LongComment && endingState == ParserStates::LongComment)
  706. {
  707. m_machine->IncFoldLevel();
  708. }
  709. if (startingState != ParserStates::StringLiteral && endingState == ParserStates::StringLiteral) //should only be true if a long string literal
  710. {
  711. m_machine->IncFoldLevel();
  712. }
  713. if (startingState == ParserStates::LongComment && endingState != ParserStates::LongComment)
  714. {
  715. m_machine->DecFoldLevel();
  716. }
  717. if (startingState == ParserStates::StringLiteral && endingState != ParserStates::StringLiteral) //should only be true if a long string literal
  718. {
  719. m_machine->DecFoldLevel();
  720. }
  721. QTBlockState newState = m_machine->GetSaveState();
  722. newState.m_blockState.m_folded = currentState.m_blockState.m_uninitialized ? 0 : currentState.m_blockState.m_folded;
  723. setCurrentBlockState(newState.m_qtBlockState);
  724. }
  725. void LUASyntaxHighlighter::SetBracketHighlighting(int openBracketPos, int closeBracketPos)
  726. {
  727. auto oldOpenBracketPos = m_openBracketPos;
  728. auto oldcloseBracketPos = m_closeBracketPos;
  729. m_openBracketPos = openBracketPos;
  730. m_closeBracketPos = closeBracketPos;
  731. auto openBlock = document()->findBlock(m_openBracketPos);
  732. auto closeBlock = document()->findBlock(m_closeBracketPos);
  733. if (openBlock.isValid())
  734. {
  735. rehighlightBlock(openBlock);
  736. }
  737. if (closeBlock.isValid())
  738. {
  739. rehighlightBlock(closeBlock);
  740. }
  741. if (oldOpenBracketPos >= 0)
  742. {
  743. openBlock = document()->findBlock(oldOpenBracketPos);
  744. if (openBlock.isValid())
  745. {
  746. rehighlightBlock(openBlock);
  747. }
  748. }
  749. if (oldcloseBracketPos >= 0)
  750. {
  751. closeBlock = document()->findBlock(oldcloseBracketPos);
  752. if (closeBlock.isValid())
  753. {
  754. rehighlightBlock(closeBlock);
  755. }
  756. }
  757. }
  758. //This code is also getting the list of lua names that are currently in scope
  759. QList<QTextEdit::ExtraSelection> LUASyntaxHighlighter::HighlightMatchingNames(const QTextCursor& cursor, const QString&) const
  760. {
  761. m_machine->SetJoiningNames(false);
  762. m_machine->SetOnIncFoldLevel([](int) {});
  763. m_machine->SetOnDecFoldLevel([](int) {});
  764. const HighlightedWords::LUAKeywordsType* keywords = nullptr;
  765. HighlightedWords::Bus::BroadcastResult(keywords, &HighlightedWords::Bus::Events::GetLUAKeywords);
  766. const HighlightedWords::LUAKeywordsType* libraryFuncs = nullptr;
  767. HighlightedWords::Bus::BroadcastResult(libraryFuncs, &HighlightedWords::Bus::Events::GetLUALibraryFunctions);
  768. auto syntaxSettings = AZ::UserSettings::CreateFind<SyntaxStyleSettings>(AZ_CRC_CE("LUA Editor Text Settings"), AZ::UserSettings::CT_GLOBAL);
  769. auto font = syntaxSettings->GetFont();
  770. QList<QTextEdit::ExtraSelection> list;
  771. QTextEdit::ExtraSelection selection;
  772. selection.cursor = cursor;
  773. QTextCharFormat textFormat;
  774. textFormat.setFont(font);
  775. textFormat.setBackground(syntaxSettings->GetCurrentIdentifierColor());
  776. selection.format = textFormat;
  777. QTBlockState mState;
  778. mState.m_qtBlockState = -1;
  779. m_machine->SetSaveState(mState);
  780. QString searchString;
  781. ParserStates matchState = ParserStates::Null;
  782. int currentScopeBlock = -1;
  783. QStringList luaNames;
  784. for (auto block = document()->begin(); block != document()->end(); block = block.next())
  785. {
  786. auto text = block.text();
  787. auto cursorPos = cursor.position() - block.position();
  788. int delayedFold = 0;
  789. m_machine->CaptureToken = [&](ParserStates state, int position, int length)
  790. {
  791. if (cursorPos >= position && cursorPos <= position + length)
  792. {
  793. if (state == ParserStates::Name)
  794. {
  795. QString match = text.mid(position, length);
  796. AZStd::string dhText(match.toUtf8().constData());
  797. if (!(keywords && keywords->find(dhText) != keywords->end()) &&
  798. !(libraryFuncs && libraryFuncs->find(dhText) != libraryFuncs->end()))
  799. {
  800. searchString = AZStd::move(match);
  801. matchState = state;
  802. currentScopeBlock = block.blockNumber();
  803. }
  804. }
  805. }
  806. if (state == ParserStates::Name)
  807. {
  808. AZStd::string dhText(text.mid(position, length).toUtf8().constData());
  809. if (m_LUAStartBlockKeywords.find(dhText) != m_LUAStartBlockKeywords.end())
  810. {
  811. m_machine->IncFoldLevel();
  812. }
  813. if (m_LUAEndBlockKeywords.find(dhText) != m_LUAEndBlockKeywords.end())
  814. {
  815. m_machine->DecFoldLevel();
  816. }
  817. if (length > 1)
  818. {
  819. luaNames.push_back(text.mid(position, length));
  820. }
  821. }
  822. };
  823. auto startingState = m_machine->GetCurrentParserState();
  824. m_machine->Parse(text);
  825. while (delayedFold > 0)
  826. {
  827. m_machine->IncFoldLevel();
  828. --delayedFold;
  829. }
  830. auto endingState = m_machine->GetCurrentParserState();
  831. if (startingState != ParserStates::LongComment && endingState == ParserStates::LongComment)
  832. {
  833. m_machine->IncFoldLevel();
  834. }
  835. if (startingState != ParserStates::StringLiteral && endingState == ParserStates::StringLiteral) //should only be true if a long string literal
  836. {
  837. m_machine->IncFoldLevel();
  838. }
  839. if (startingState == ParserStates::LongComment && endingState != ParserStates::LongComment)
  840. {
  841. m_machine->DecFoldLevel();
  842. }
  843. if (startingState == ParserStates::StringLiteral && endingState != ParserStates::StringLiteral) //should only be true if a long string literal
  844. {
  845. m_machine->DecFoldLevel();
  846. }
  847. }
  848. m_machine->SetOnIncFoldLevel([](int) {});
  849. m_machine->SetOnDecFoldLevel([](int) {});
  850. if (matchState != ParserStates::Null)
  851. {
  852. for (auto block = document()->begin(); block != document()->end(); block = block.next())
  853. {
  854. auto text = block.text();
  855. m_machine->CaptureToken = [&](ParserStates state, int position, int length)
  856. {
  857. QString token = text.mid(position, length);
  858. if (state == matchState)
  859. {
  860. if (token == searchString)
  861. {
  862. selection.cursor.setPosition(position + block.position());
  863. selection.cursor.setPosition(position + block.position() + length, QTextCursor::MoveMode::KeepAnchor);
  864. list.push_back(selection);
  865. }
  866. }
  867. };
  868. m_machine->Parse(text);
  869. }
  870. }
  871. if (currentScopeBlock != -1 && currentScopeBlock != m_currentScopeBlock)
  872. {
  873. LUANamesInScopeChanged(luaNames);
  874. m_currentScopeBlock = currentScopeBlock;
  875. }
  876. return list;
  877. }
  878. QString LUASyntaxHighlighter::GetLUAName(const QTextCursor& cursor)
  879. {
  880. auto block = document()->findBlock(cursor.position());
  881. if (!block.isValid())
  882. {
  883. return "";
  884. }
  885. auto prevBlock = block.previous();
  886. QTBlockState prevState;
  887. if (prevBlock.isValid())
  888. {
  889. prevState.m_qtBlockState = prevBlock.userState();
  890. }
  891. else
  892. {
  893. prevState.m_qtBlockState = -1;
  894. }
  895. m_machine->SetSaveState(prevState);
  896. m_machine->SetJoiningNames(true);
  897. m_machine->SetOnIncFoldLevel([](int) {});
  898. m_machine->SetOnDecFoldLevel([](int) {});
  899. auto cursorPos = cursor.position() - block.position();
  900. auto text = block.text();
  901. QString result;
  902. m_machine->CaptureToken = [&](ParserStates state, int position, int length)
  903. {
  904. if (cursorPos >= position && cursorPos <= position + length)
  905. {
  906. if (state == ParserStates::Name)
  907. {
  908. result = text.mid(position, length);
  909. }
  910. }
  911. };
  912. m_machine->Parse(block.text());
  913. return result;
  914. }
  915. }