LUAEditorPlainTextEdit.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  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 "LUAEditorPlainTextEdit.hxx"
  9. #include "LUAEditorBlockState.h"
  10. #include "LUAEditorStyleMessages.h"
  11. #include <Source/LUA/CodeCompletion/LUACompletionModel.hxx>
  12. #include <Source/LUA/CodeCompletion/LUACompleter.hxx>
  13. #include <Source/LUA/moc_LUAEditorPlainTextEdit.cpp>
  14. #include <Source/LUA/LUAEditorView.hxx>
  15. #include "LUAEditorContextMessages.h"
  16. #include <QPainter>
  17. #include <QTextBlock>
  18. #include <QScrollBar>
  19. #include <QAbstractItemView>
  20. #include <QApplication>
  21. #include <QTextDocumentFragment>
  22. #include <QMimeData>
  23. #include <QClipboard>
  24. namespace LUAEditor
  25. {
  26. LUAEditorPlainTextEdit::LUAEditorPlainTextEdit(QWidget* pParent)
  27. : QPlainTextEdit(pParent)
  28. {
  29. m_completionModel = aznew CompletionModel(this);
  30. m_completer = aznew Completer(m_completionModel, this);
  31. m_completer->setWidget(this);
  32. connect(m_completer, static_cast<void (QCompleter::*)(const QString&)>(&QCompleter::activated), this, &LUAEditorPlainTextEdit::CompletionSelected);
  33. }
  34. LUAEditorPlainTextEdit::~LUAEditorPlainTextEdit()
  35. {
  36. delete m_completer;
  37. delete m_completionModel;
  38. }
  39. QRectF LUAEditorPlainTextEdit::GetBlockBoundingGeometry(const QTextBlock& block) const
  40. {
  41. auto result = blockBoundingGeometry(block);
  42. result.translate(contentOffset());
  43. return result;
  44. }
  45. void LUAEditorPlainTextEdit::ForEachVisibleBlock(AZStd::function<void(QTextBlock& block, const QRectF&)> operation) const
  46. {
  47. for (auto block = document()->begin(); block != document()->end(); block = block.next())
  48. {
  49. auto blockRect = GetBlockBoundingGeometry(block);
  50. if (block.isVisible() && blockRect.bottom() > 0 && blockRect.top() < size().height())
  51. {
  52. operation(block, blockRect);
  53. }
  54. }
  55. }
  56. void LUAEditorPlainTextEdit::mouseDoubleClickEvent(QMouseEvent* event)
  57. {
  58. auto& mousePos = event->localPos();
  59. QTextBlock blockClicked;
  60. // any block could be hidden, so have to loop through to be sure
  61. ForEachVisibleBlock(
  62. [&](const QTextBlock& block, const QRectF& blockRect)
  63. {
  64. if (mousePos.y() >= blockRect.top() && mousePos.y() <= blockRect.bottom())
  65. {
  66. blockClicked = block;
  67. }
  68. });
  69. event->ignore();
  70. if (blockClicked.isValid())
  71. {
  72. emit BlockDoubleClicked(event, blockClicked);
  73. }
  74. if (!event->isAccepted())
  75. {
  76. QPlainTextEdit::mouseDoubleClickEvent(event);
  77. }
  78. event->accept();
  79. }
  80. void LUAEditorPlainTextEdit::scrollContentsBy(int x, int y)
  81. {
  82. QPlainTextEdit::scrollContentsBy(x, y);
  83. emit Scrolled();
  84. }
  85. void LUAEditorPlainTextEdit::focusInEvent(QFocusEvent* pEvent)
  86. {
  87. QPlainTextEdit::focusInEvent(pEvent);
  88. emit FocusChanged(true);
  89. }
  90. void LUAEditorPlainTextEdit::focusOutEvent(QFocusEvent* pEvent)
  91. {
  92. QPlainTextEdit::focusOutEvent(pEvent);
  93. emit FocusChanged(false);
  94. }
  95. void LUAEditorPlainTextEdit::paintEvent(QPaintEvent* event)
  96. {
  97. QPlainTextEdit::paintEvent(event);
  98. auto colors = AZ::UserSettings::CreateFind<SyntaxStyleSettings>(AZ_CRC_CE("LUA Editor Text Settings"), AZ::UserSettings::CT_GLOBAL);
  99. QPainter painter(viewport());
  100. auto metrics = QFontMetrics(font());
  101. int descent = metrics.descent() - 1;
  102. auto oldPen = painter.pen();
  103. auto cursor = textCursor();
  104. auto currentBlock = document()->findBlock(cursor.position());
  105. ForEachVisibleBlock([&](QTextBlock& block, const QRectF& bounds)
  106. {
  107. QTBlockState state;
  108. state.m_qtBlockState = block.userState();
  109. if (state.m_blockState.m_folded)
  110. {
  111. painter.setPen(colors->GetFoldingLineColor());
  112. auto iBounds = bounds.toRect();
  113. painter.drawLine(iBounds.left(), iBounds.bottom() + descent, iBounds.right(), iBounds.bottom() + descent);
  114. }
  115. if (currentBlock.isValid() && currentBlock.blockNumber() == block.blockNumber())
  116. {
  117. painter.setPen(colors->GetCurrentLineOutlineColor());
  118. auto iBounds = bounds.toRect();
  119. iBounds.setLeft(iBounds.left() + horizontalScrollBar()->value());
  120. iBounds.setWidth(viewport()->size().width() - 1);
  121. painter.drawRect(iBounds);
  122. }
  123. });
  124. painter.setPen(oldPen);
  125. }
  126. void LUAEditorPlainTextEdit::keyPressEvent(QKeyEvent* event)
  127. {
  128. if (event == QKeySequence::Cut)
  129. {
  130. auto cursor = textCursor();
  131. if (cursor.hasSelection())
  132. {
  133. QPlainTextEdit::keyPressEvent(event);
  134. }
  135. else
  136. {
  137. auto block = document()->findBlock(cursor.position());
  138. if (block.isValid())
  139. {
  140. cursor.setPosition(block.position() - 1);
  141. cursor.setPosition(block.position() + block.length() - 1, QTextCursor::MoveMode::KeepAnchor);
  142. if (!block.text().trimmed().isEmpty())
  143. {
  144. QApplication::clipboard()->setText(cursor.selectedText());
  145. }
  146. cursor.removeSelectedText();
  147. }
  148. }
  149. }
  150. else
  151. {
  152. if (m_completer->popup()->isVisible())
  153. {
  154. //let completer handle accepting the selected completion
  155. //completion selected
  156. if (m_completer->popup()->currentIndex().isValid())
  157. {
  158. switch (event->key())
  159. {
  160. case Qt::Key_Enter:
  161. case Qt::Key_Return:
  162. case Qt::Key_Escape:
  163. case Qt::Key_Tab:
  164. case Qt::Key_Backtab:
  165. if (m_completer->currentCompletion() != m_completer->completionPrefix())
  166. {
  167. event->ignore();
  168. return;
  169. }
  170. break;
  171. default:
  172. break;
  173. }
  174. }
  175. else //completion not selected
  176. {
  177. //let completer handle accepting the selected completion
  178. switch (event->key())
  179. {
  180. case Qt::Key_Escape:
  181. {
  182. event->ignore();
  183. return;
  184. }
  185. break;
  186. default:
  187. break;
  188. }
  189. }
  190. }
  191. if (HandleNewline(event))
  192. {
  193. return;
  194. }
  195. if (HandleHomeKeyPress(event))
  196. {
  197. return;
  198. }
  199. if (HandleIndentKeyPress(event))
  200. {
  201. return;
  202. }
  203. if ( (m_getLUAName && event->key() == Qt::Key::Key_Space) && (event->modifiers() & Qt::KeyboardModifier::ControlModifier) )
  204. {
  205. auto bounds = cursorRect();
  206. bounds.setLeft(bounds.left());
  207. bounds.setRight(bounds.left() + 250);
  208. m_completer->setCompletionPrefix(m_getLUAName(textCursor()));
  209. m_completer->complete(bounds);
  210. }
  211. else
  212. {
  213. QPlainTextEdit::keyPressEvent(event);
  214. if (0 == (event->modifiers() & (Qt::KeyboardModifier::ControlModifier | Qt::KeyboardModifier::MetaModifier)))
  215. {
  216. // don't proceed into this block and pop up the completer if a hotkey like CTRL+C / CTRL+V is being used.
  217. LUAViewWidget* parentWidget = qobject_cast<LUAViewWidget*>(parent());
  218. auto luaName = m_getLUAName(textCursor());
  219. if (!luaName.isEmpty() && parentWidget && parentWidget->IsAutoCompletionEnabled())
  220. {
  221. m_completer->setCompletionPrefix(luaName);
  222. if ((event->key() >= Qt::Key::Key_0 && event->key() <= Qt::Key::Key_9) ||
  223. (event->key() >= Qt::Key::Key_A && event->key() <= Qt::Key::Key_Z) ||
  224. (event->key() == Qt::Key::Key_Period) || (event->key() == Qt::Key::Key_Colon) ||
  225. (event->key() == Qt::Key::Key_Backspace) || (event->key() == Qt::Key::Key_Delete) ||
  226. m_completer->popup()->isVisible())
  227. {
  228. if (m_completer->completionCount() == 1 && m_completer->currentCompletion() == m_completer->completionPrefix()) //we have a match already
  229. {
  230. m_completer->popup()->hide();
  231. }
  232. else
  233. {
  234. auto bounds = cursorRect();
  235. bounds.setLeft(bounds.left());
  236. bounds.setRight(bounds.left() + 250);
  237. m_completer->complete(bounds);
  238. }
  239. }
  240. }
  241. else
  242. {
  243. m_completer->popup()->hide();
  244. }
  245. }
  246. }
  247. }
  248. }
  249. bool LUAEditorPlainTextEdit::HandleNewline(QKeyEvent* event)
  250. {
  251. if (isReadOnly())
  252. {
  253. return false;
  254. }
  255. if (event->key() == Qt::Key::Key_Enter || event->key() == Qt::Key::Key_Return)
  256. {
  257. auto cursor = textCursor();
  258. auto block = document()->findBlock(cursor.position());
  259. if (block.isValid())
  260. {
  261. int cursorStartColumn = cursor.columnNumber();
  262. cursor.beginEditBlock();
  263. cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
  264. cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
  265. block = document()->findBlock(cursor.position());
  266. if (block.isValid())
  267. {
  268. QString text = block.text();
  269. QString leadingSpacing;
  270. // Count the number of leading tabs
  271. int position = 0;
  272. while (position < text.size() && (text.at(position) == '\t' || text.at(position) == ' ')) { leadingSpacing += text.at(position); ++position; }
  273. QString left = text.left(cursorStartColumn);
  274. QString trail = text.right(text.size() - cursorStartColumn);
  275. // Add the newline and as many tabs leading tabs as we counted
  276. cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
  277. cursor.removeSelectedText();
  278. cursor.insertText(left);
  279. cursor.insertText("\n");
  280. if (leadingSpacing.size() > 0)
  281. {
  282. cursor.insertText(leadingSpacing);
  283. }
  284. cursor.insertText(trail);
  285. cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
  286. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, position);
  287. cursor.endEditBlock();
  288. setTextCursor(cursor);
  289. return true;
  290. }
  291. cursor.endEditBlock();
  292. }
  293. }
  294. return false;
  295. }
  296. bool LUAEditorPlainTextEdit::HandleHomeKeyPress(QKeyEvent* event)
  297. {
  298. if (event->key() == Qt::Key::Key_Home)
  299. {
  300. auto cursor = textCursor();
  301. auto block = document()->findBlock(cursor.position());
  302. if (block.isValid())
  303. {
  304. // Ctrl+Home goes to start of document.
  305. if (event->modifiers() & Qt::KeyboardModifier::ControlModifier)
  306. {
  307. if (event->modifiers() & Qt::KeyboardModifier::ShiftModifier)
  308. {
  309. int position = cursor.position();
  310. cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
  311. cursor.setPosition(position, QTextCursor::KeepAnchor);
  312. }
  313. else
  314. {
  315. cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
  316. }
  317. setTextCursor(cursor);
  318. return true;
  319. }
  320. if (cursor.columnNumber() > 0)
  321. {
  322. QString text = block.text();
  323. QStringRef line = QStringRef(&text, 0, cursor.columnNumber());
  324. // Count the number of leading whitespace characters
  325. int offset = 0;
  326. while (offset < line.size() && (line.at(offset) == ' ' || line.at(offset) == '\t')) { ++offset; }
  327. int column = cursor.columnNumber();
  328. QTextCursor::MoveMode moveMode = QTextCursor::MoveMode::MoveAnchor;
  329. if (event->modifiers() & Qt::KeyboardModifier::ShiftModifier)
  330. {
  331. moveMode = QTextCursor::MoveMode::KeepAnchor;
  332. }
  333. cursor.movePosition(QTextCursor::StartOfLine, moveMode);
  334. // If the cursor was originally beyond the first white space, stop it at the first non-whitespace character.
  335. if (column > offset)
  336. {
  337. cursor.movePosition(QTextCursor::Right, moveMode, offset);
  338. }
  339. }
  340. else
  341. {
  342. QString text = block.text();
  343. cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
  344. int position = cursor.position();
  345. QStringRef line = QStringRef(&text, 0, position);
  346. int offset = 0;
  347. while (offset < line.size() && (line.at(offset) == ' ' || line.at(offset) == '\t')) { ++offset; }
  348. cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
  349. cursor.movePosition(QTextCursor::Right, QTextCursor::MoveMode::MoveAnchor, offset);
  350. }
  351. setTextCursor(cursor);
  352. return true;
  353. }
  354. }
  355. return false;
  356. }
  357. bool LUAEditorPlainTextEdit::HandleIndentKeyPress(QKeyEvent* event)
  358. {
  359. if (event->key() == Qt::Key::Key_Tab || event->key() == Qt::Key::Key_Backtab)
  360. {
  361. bool addIndent = (event->key() == Qt::Key::Key_Tab);
  362. QString tabString = m_useSpaces ? QString(" ").repeated(m_tabSize) : "\t";
  363. auto cursor = textCursor();
  364. if (cursor.hasSelection())
  365. {
  366. int anchor = cursor.anchor();
  367. int position = cursor.position();
  368. if (anchor > position)
  369. {
  370. std::swap(anchor, position);
  371. }
  372. cursor.setPosition(anchor);
  373. cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
  374. anchor = cursor.position();
  375. // save a new anchor at the beginning of the line of the selected text
  376. cursor.setPosition(anchor);
  377. cursor.setPosition(position, QTextCursor::KeepAnchor);
  378. // set a new selection with the new beginning
  379. QString text = cursor.selection().toPlainText();
  380. if (m_useSpaces)
  381. {
  382. // Replace tabs with spaces and adjust position
  383. int tabs = text.count("\t");
  384. text.replace("\t", tabString);
  385. position += tabs * (m_tabSize - 1);
  386. }
  387. QStringList list = text.split("\n");
  388. // get the selected text and split into lines
  389. for (int i = 0; i < list.count(); i++)
  390. {
  391. QString& line = list[i];
  392. if (addIndent)
  393. {
  394. line.insert(0, tabString);
  395. position += tabString.length();
  396. }
  397. else
  398. {
  399. if (line.startsWith(tabString))
  400. {
  401. line.remove(0, tabString.length());
  402. position -= tabString.length();
  403. }
  404. }
  405. }
  406. text = list.join("\n");
  407. cursor.beginEditBlock();
  408. {
  409. cursor.removeSelectedText();
  410. cursor.insertText(text);
  411. }
  412. cursor.endEditBlock();
  413. // put the new text back
  414. cursor.setPosition(anchor);
  415. cursor.setPosition(position, QTextCursor::KeepAnchor);
  416. // reselect the text for more indents
  417. setTextCursor(cursor);
  418. return true;
  419. }
  420. else
  421. {
  422. if (addIndent && !isReadOnly())
  423. {
  424. cursor.insertText(tabString);
  425. return true;
  426. }
  427. else if (event->key() == Qt::Key::Key_Backtab)
  428. {
  429. int position = cursor.position();
  430. int columnNumber = cursor.columnNumber();
  431. int removeCount = 1;
  432. cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
  433. QString text = cursor.block().text();
  434. if (text[columnNumber] != '\t' || text.midRef(columnNumber, m_tabSize) != tabString)
  435. {
  436. if (columnNumber > 0 && text[columnNumber - 1] == '\t')
  437. {
  438. --columnNumber;
  439. --position;
  440. }
  441. else if (columnNumber > 0 && text.midRef(columnNumber - m_tabSize, m_tabSize) == tabString)
  442. {
  443. columnNumber -= tabString.length();
  444. position -= tabString.length();
  445. removeCount = tabString.length();
  446. }
  447. else
  448. {
  449. // No tab to remove, but consider it handled to avoid adding a tab
  450. return true;
  451. }
  452. }
  453. // Remove the tab
  454. text.remove(columnNumber, removeCount);
  455. // Select the entire line, we'll replace it with the modified text
  456. cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
  457. cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
  458. // Replace the line with the new text
  459. cursor.beginEditBlock();
  460. {
  461. cursor.removeSelectedText();
  462. cursor.insertText(text);
  463. }
  464. cursor.endEditBlock();
  465. // Restore the position to where it was
  466. cursor.setPosition(position);
  467. setTextCursor(cursor);
  468. return true;
  469. }
  470. }
  471. }
  472. return false;
  473. }
  474. void LUAEditorPlainTextEdit::wheelEvent(QWheelEvent* event)
  475. {
  476. if (event->modifiers() & Qt::ControlModifier)
  477. {
  478. const int delta = event->angleDelta().y();
  479. if (delta < 0)
  480. {
  481. ZoomOut();
  482. }
  483. else if (delta > 0)
  484. {
  485. ZoomIn();
  486. }
  487. return;
  488. }
  489. QPlainTextEdit::wheelEvent(event);
  490. }
  491. void LUAEditorPlainTextEdit::UpdateFont(QFont font, int tabSize)
  492. {
  493. QTextCursor currentCursor = textCursor();
  494. QFontMetrics metrics(font);
  495. QTextCharFormat characterFormat;
  496. characterFormat.setFont(font);
  497. selectAll();
  498. setCurrentCharFormat(characterFormat);
  499. setTabStopDistance(metrics.horizontalAdvance(' ') * tabSize);
  500. setTextCursor(currentCursor);
  501. }
  502. void LUAEditorPlainTextEdit::CompletionSelected(const QString& text)
  503. {
  504. QString prefix = m_completer->completionPrefix();
  505. QTextCursor cursor = textCursor();
  506. // lastIndexOf will return -1 if not found, so subtracting index of lastPeriod + 1,
  507. // the math will always work regardless of whether the prefix contains a . or not
  508. int lastPeriod = prefix.lastIndexOf('.');
  509. int charactersToReplace = prefix.length() - (lastPeriod + 1);
  510. cursor.setPosition(cursor.position() - charactersToReplace, QTextCursor::KeepAnchor);
  511. cursor.insertText(text);
  512. }
  513. void LUAEditorPlainTextEdit::OnScopeNamesUpdated(const QStringList& scopeNames)
  514. {
  515. m_completionModel->OnScopeNamesUpdated(scopeNames);
  516. }
  517. void LUAEditorPlainTextEdit::dropEvent(QDropEvent* e)
  518. {
  519. if (e->mimeData()->hasUrls())
  520. {
  521. QList<QUrl> urls = e->mimeData()->urls();
  522. for (int idx = 0; idx < urls.count(); ++idx)
  523. {
  524. QString path = urls[idx].toLocalFile();
  525. AZ_TracePrintf("Debug", "URL: %s\n", path.toUtf8().data());
  526. AZStd::string assetId(path.toUtf8().data());
  527. Context_DocumentManagement::Bus::Broadcast(&Context_DocumentManagement::Bus::Events::OnLoadDocument, assetId, true);
  528. }
  529. }
  530. else
  531. {
  532. QPlainTextEdit::dropEvent(e);
  533. }
  534. }
  535. }