LUAEditorSyntaxHighlighter.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  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("LUA Editor Text Settings", 0xb6e15565), AZ::UserSettings::CT_GLOBAL);
  497. // Match against ; : , . = * - + /
  498. {
  499. HighlightingRule rule;
  500. rule.pattern = QRegularExpression(R"([\;\:\,\.\=\*\-\+\/])");
  501. rule.colorCB = [colors]()
  502. {
  503. return colors->GetSpecialCharacterColor();
  504. };
  505. m_highlightingRules.push_back(rule);
  506. }
  507. // Match against parenthesis and brackets
  508. {
  509. HighlightingRule rule;
  510. rule.pattern = QRegularExpression(R"([\(\)\{\}\[\]])");
  511. rule.colorCB = [colors]()
  512. {
  513. return colors->GetBracketColor();
  514. };
  515. m_highlightingRules.push_back(rule);
  516. }
  517. // Match against local and self keywords
  518. {
  519. HighlightingRule rule;
  520. rule.pattern = QRegularExpression(R"(\bself\b|\blocal\b)");
  521. rule.colorCB = [colors]()
  522. {
  523. return colors->GetSpecialKeywordColor();
  524. };
  525. m_highlightingRules.push_back(rule);
  526. }
  527. // Match methods and definitions. Any word which ends with '('
  528. {
  529. HighlightingRule rule;
  530. rule.pattern = QRegularExpression(R"(\b[A-Za-z0-9_]+(?=\())");
  531. rule.colorCB = [colors]()
  532. {
  533. return colors->GetMethodColor();
  534. };
  535. m_highlightingRules.push_back(rule);
  536. }
  537. }
  538. void LUASyntaxHighlighter::AddBlockKeywords()
  539. {
  540. //these don't catch tables. that is handled in the null machine state.
  541. m_LUAStartBlockKeywords.clear();
  542. AZStd::string startKeywords[] = {
  543. {"do"}, {"if"}, {"function"}, {"repeat"}
  544. };
  545. m_LUAStartBlockKeywords.insert(std::begin(startKeywords), std::end(startKeywords));
  546. m_LUAEndBlockKeywords.clear();
  547. AZStd::string endKeywords[] = {
  548. {"end"}, {"until"}
  549. };
  550. m_LUAEndBlockKeywords.insert(std::begin(endKeywords), std::end(endKeywords));
  551. }
  552. LUASyntaxHighlighter::~LUASyntaxHighlighter()
  553. {
  554. azdestroy(m_machine);
  555. }
  556. void LUASyntaxHighlighter::highlightBlock(const QString& text)
  557. {
  558. m_machine->SetJoiningNames(true);
  559. m_machine->SetOnIncFoldLevel([](int) {});
  560. m_machine->SetOnDecFoldLevel([](int) {});
  561. auto colors = AZ::UserSettings::CreateFind<SyntaxStyleSettings>(AZ_CRC("LUA Editor Text Settings", 0xb6e15565), AZ::UserSettings::CT_GLOBAL);
  562. const HighlightedWords::LUAKeywordsType* keywords = nullptr;
  563. HighlightedWords::Bus::BroadcastResult(keywords, &HighlightedWords::Bus::Events::GetLUAKeywords);
  564. const HighlightedWords::LUAKeywordsType* libraryFuncs = nullptr;
  565. HighlightedWords::Bus::BroadcastResult(libraryFuncs, &HighlightedWords::Bus::Events::GetLUALibraryFunctions);
  566. auto cBlock = currentBlock();
  567. QTBlockState currentState;
  568. currentState.m_qtBlockState = currentBlockState();
  569. QTextCharFormat textFormat;
  570. textFormat.setFont(colors->GetFont());
  571. textFormat.setForeground(colors->GetTextColor());
  572. setFormat(0, cBlock.length(), textFormat);
  573. QTextCharFormat spaceFormat = QTextCharFormat();
  574. spaceFormat.setForeground(colors->GetTextWhitespaceColor());
  575. QRegExp tabsAndSpaces("( |\t)+");
  576. int index = tabsAndSpaces.indexIn(text);
  577. while (index >= 0)
  578. {
  579. int length = tabsAndSpaces.matchedLength();
  580. setFormat(index, length, spaceFormat);
  581. index = tabsAndSpaces.indexIn(text, index + length);
  582. }
  583. QTBlockState prevState;
  584. prevState.m_qtBlockState = previousBlockState();
  585. m_machine->SetSaveState(prevState);
  586. auto startingState = m_machine->GetCurrentParserState();
  587. m_machine->CaptureToken = [&](ParserStates state, int position, int length)
  588. {
  589. if (state == ParserStates::Name)
  590. {
  591. AZStd::string dhText(text.mid(position, length).toUtf8().constData());
  592. if (keywords && keywords->find(dhText) != keywords->end())
  593. {
  594. textFormat.setForeground(colors->GetKeywordColor());
  595. setFormat(position, length, textFormat);
  596. }
  597. else if (libraryFuncs && libraryFuncs->find(dhText) != libraryFuncs->end())
  598. {
  599. textFormat.setForeground(colors->GetLibraryColor());
  600. setFormat(position, length, textFormat);
  601. }
  602. else
  603. {
  604. textFormat.setForeground(colors->GetTextColor());
  605. setFormat(position, length, textFormat);
  606. }
  607. if (m_LUAStartBlockKeywords.find(dhText) != m_LUAStartBlockKeywords.end())
  608. {
  609. m_machine->IncFoldLevel();
  610. }
  611. if (m_LUAEndBlockKeywords.find(dhText) != m_LUAEndBlockKeywords.end())
  612. {
  613. m_machine->DecFoldLevel();
  614. }
  615. }
  616. if (state == ParserStates::ShortComment || state == ParserStates::LongComment)
  617. {
  618. textFormat.setForeground(colors->GetCommentColor());
  619. setFormat(position, length, textFormat);
  620. }
  621. if (state == ParserStates::Number || state == ParserStates::NumberHex)
  622. {
  623. textFormat.setForeground(colors->GetNumberColor());
  624. setFormat(position, length, textFormat);
  625. }
  626. if (state == ParserStates::StringLiteral)
  627. {
  628. textFormat.setForeground(colors->GetStringLiteralColor());
  629. setFormat(position, length, textFormat);
  630. }
  631. const bool canRunRegEx = state == ParserStates::Null || state == ParserStates::Name;
  632. if (!canRunRegEx)
  633. return;
  634. for (const HighlightingRule& rule : m_highlightingRules)
  635. {
  636. QRegularExpressionMatchIterator i = rule.pattern.globalMatch(text);
  637. while (i.hasNext())
  638. {
  639. QRegularExpressionMatch match = i.next();
  640. textFormat.setForeground(rule.colorCB());
  641. setFormat(match.capturedStart(), match.capturedLength(), textFormat);
  642. }
  643. }
  644. };
  645. m_machine->Parse(text);
  646. // Selected bracket matching highlighting
  647. if (m_openBracketPos >= 0 && m_closeBracketPos >= 0)
  648. {
  649. if (cBlock.contains(m_openBracketPos))
  650. {
  651. setFormat(m_openBracketPos - cBlock.position(), 1, colors->GetSelectedBracketColor());
  652. }
  653. if (cBlock.contains(m_closeBracketPos))
  654. {
  655. setFormat(m_closeBracketPos - cBlock.position(), 1, colors->GetSelectedBracketColor());
  656. }
  657. }
  658. else if (m_openBracketPos >= 0)
  659. {
  660. if (cBlock.contains(m_openBracketPos))
  661. {
  662. setFormat(m_openBracketPos - cBlock.position(), 1, colors->GetUnmatchedBracketColor());
  663. }
  664. if (cBlock.contains(m_closeBracketPos))
  665. {
  666. setFormat(m_closeBracketPos - cBlock.position(), 1, colors->GetUnmatchedBracketColor());
  667. }
  668. }
  669. auto endingState = m_machine->GetCurrentParserState();
  670. if (startingState != ParserStates::LongComment && endingState == ParserStates::LongComment)
  671. {
  672. m_machine->IncFoldLevel();
  673. }
  674. if (startingState != ParserStates::StringLiteral && endingState == ParserStates::StringLiteral) //should only be true if a long string literal
  675. {
  676. m_machine->IncFoldLevel();
  677. }
  678. if (startingState == ParserStates::LongComment && endingState != ParserStates::LongComment)
  679. {
  680. m_machine->DecFoldLevel();
  681. }
  682. if (startingState == ParserStates::StringLiteral && endingState != ParserStates::StringLiteral) //should only be true if a long string literal
  683. {
  684. m_machine->DecFoldLevel();
  685. }
  686. QTBlockState newState = m_machine->GetSaveState();
  687. newState.m_blockState.m_folded = currentState.m_blockState.m_uninitialized ? 0 : currentState.m_blockState.m_folded;
  688. setCurrentBlockState(newState.m_qtBlockState);
  689. }
  690. void LUASyntaxHighlighter::SetBracketHighlighting(int openBracketPos, int closeBracketPos)
  691. {
  692. auto oldOpenBracketPos = m_openBracketPos;
  693. auto oldcloseBracketPos = m_closeBracketPos;
  694. m_openBracketPos = openBracketPos;
  695. m_closeBracketPos = closeBracketPos;
  696. auto openBlock = document()->findBlock(m_openBracketPos);
  697. auto closeBlock = document()->findBlock(m_closeBracketPos);
  698. if (openBlock.isValid())
  699. {
  700. rehighlightBlock(openBlock);
  701. }
  702. if (closeBlock.isValid())
  703. {
  704. rehighlightBlock(closeBlock);
  705. }
  706. if (oldOpenBracketPos >= 0)
  707. {
  708. openBlock = document()->findBlock(oldOpenBracketPos);
  709. if (openBlock.isValid())
  710. {
  711. rehighlightBlock(openBlock);
  712. }
  713. }
  714. if (oldcloseBracketPos >= 0)
  715. {
  716. closeBlock = document()->findBlock(oldcloseBracketPos);
  717. if (closeBlock.isValid())
  718. {
  719. rehighlightBlock(closeBlock);
  720. }
  721. }
  722. }
  723. //This code is also getting the list of lua names that are currently in scope
  724. QList<QTextEdit::ExtraSelection> LUASyntaxHighlighter::HighlightMatchingNames(const QTextCursor& cursor, const QString&) const
  725. {
  726. m_machine->SetJoiningNames(false);
  727. m_machine->SetOnIncFoldLevel([](int) {});
  728. m_machine->SetOnDecFoldLevel([](int) {});
  729. const HighlightedWords::LUAKeywordsType* keywords = nullptr;
  730. HighlightedWords::Bus::BroadcastResult(keywords, &HighlightedWords::Bus::Events::GetLUAKeywords);
  731. const HighlightedWords::LUAKeywordsType* libraryFuncs = nullptr;
  732. HighlightedWords::Bus::BroadcastResult(libraryFuncs, &HighlightedWords::Bus::Events::GetLUALibraryFunctions);
  733. auto syntaxSettings = AZ::UserSettings::CreateFind<SyntaxStyleSettings>(AZ_CRC("LUA Editor Text Settings", 0xb6e15565), AZ::UserSettings::CT_GLOBAL);
  734. auto font = syntaxSettings->GetFont();
  735. QList<QTextEdit::ExtraSelection> list;
  736. QTextEdit::ExtraSelection selection;
  737. selection.cursor = cursor;
  738. QTextCharFormat textFormat;
  739. textFormat.setFont(font);
  740. textFormat.setBackground(syntaxSettings->GetCurrentIdentifierColor());
  741. selection.format = textFormat;
  742. QTBlockState mState;
  743. mState.m_qtBlockState = -1;
  744. m_machine->SetSaveState(mState);
  745. QString searchString;
  746. ParserStates matchState = ParserStates::Null;
  747. int currentScopeBlock = -1;
  748. QStringList luaNames;
  749. for (auto block = document()->begin(); block != document()->end(); block = block.next())
  750. {
  751. auto text = block.text();
  752. auto cursorPos = cursor.position() - block.position();
  753. int delayedFold = 0;
  754. m_machine->CaptureToken = [&](ParserStates state, int position, int length)
  755. {
  756. if (cursorPos >= position && cursorPos <= position + length)
  757. {
  758. if (state == ParserStates::Name)
  759. {
  760. QString match = text.mid(position, length);
  761. AZStd::string dhText(match.toUtf8().constData());
  762. if (!(keywords && keywords->find(dhText) != keywords->end()) &&
  763. !(libraryFuncs && libraryFuncs->find(dhText) != libraryFuncs->end()))
  764. {
  765. searchString = AZStd::move(match);
  766. matchState = state;
  767. currentScopeBlock = block.blockNumber();
  768. }
  769. }
  770. }
  771. if (state == ParserStates::Name)
  772. {
  773. AZStd::string dhText(text.mid(position, length).toUtf8().constData());
  774. if (m_LUAStartBlockKeywords.find(dhText) != m_LUAStartBlockKeywords.end())
  775. {
  776. m_machine->IncFoldLevel();
  777. }
  778. if (m_LUAEndBlockKeywords.find(dhText) != m_LUAEndBlockKeywords.end())
  779. {
  780. m_machine->DecFoldLevel();
  781. }
  782. if (length > 1)
  783. {
  784. luaNames.push_back(text.mid(position, length));
  785. }
  786. }
  787. };
  788. auto startingState = m_machine->GetCurrentParserState();
  789. m_machine->Parse(text);
  790. while (delayedFold > 0)
  791. {
  792. m_machine->IncFoldLevel();
  793. --delayedFold;
  794. }
  795. auto endingState = m_machine->GetCurrentParserState();
  796. if (startingState != ParserStates::LongComment && endingState == ParserStates::LongComment)
  797. {
  798. m_machine->IncFoldLevel();
  799. }
  800. if (startingState != ParserStates::StringLiteral && endingState == ParserStates::StringLiteral) //should only be true if a long string literal
  801. {
  802. m_machine->IncFoldLevel();
  803. }
  804. if (startingState == ParserStates::LongComment && endingState != ParserStates::LongComment)
  805. {
  806. m_machine->DecFoldLevel();
  807. }
  808. if (startingState == ParserStates::StringLiteral && endingState != ParserStates::StringLiteral) //should only be true if a long string literal
  809. {
  810. m_machine->DecFoldLevel();
  811. }
  812. }
  813. m_machine->SetOnIncFoldLevel([](int) {});
  814. m_machine->SetOnDecFoldLevel([](int) {});
  815. if (matchState != ParserStates::Null)
  816. {
  817. for (auto block = document()->begin(); block != document()->end(); block = block.next())
  818. {
  819. auto text = block.text();
  820. m_machine->CaptureToken = [&](ParserStates state, int position, int length)
  821. {
  822. QString token = text.mid(position, length);
  823. if (state == matchState)
  824. {
  825. if (token == searchString)
  826. {
  827. selection.cursor.setPosition(position + block.position());
  828. selection.cursor.setPosition(position + block.position() + length, QTextCursor::MoveMode::KeepAnchor);
  829. list.push_back(selection);
  830. }
  831. }
  832. };
  833. m_machine->Parse(text);
  834. }
  835. }
  836. if (currentScopeBlock != -1 && currentScopeBlock != m_currentScopeBlock)
  837. {
  838. LUANamesInScopeChanged(luaNames);
  839. m_currentScopeBlock = currentScopeBlock;
  840. }
  841. return list;
  842. }
  843. QString LUASyntaxHighlighter::GetLUAName(const QTextCursor& cursor)
  844. {
  845. auto block = document()->findBlock(cursor.position());
  846. if (!block.isValid())
  847. {
  848. return "";
  849. }
  850. auto prevBlock = block.previous();
  851. QTBlockState prevState;
  852. if (prevBlock.isValid())
  853. {
  854. prevState.m_qtBlockState = prevBlock.userState();
  855. }
  856. else
  857. {
  858. prevState.m_qtBlockState = -1;
  859. }
  860. m_machine->SetSaveState(prevState);
  861. m_machine->SetJoiningNames(true);
  862. m_machine->SetOnIncFoldLevel([](int) {});
  863. m_machine->SetOnDecFoldLevel([](int) {});
  864. auto cursorPos = cursor.position() - block.position();
  865. auto text = block.text();
  866. QString result;
  867. m_machine->CaptureToken = [&](ParserStates state, int position, int length)
  868. {
  869. if (cursorPos >= position && cursorPos <= position + length)
  870. {
  871. if (state == ParserStates::Name)
  872. {
  873. result = text.mid(position, length);
  874. }
  875. }
  876. };
  877. m_machine->Parse(block.text());
  878. return result;
  879. }
  880. }