mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
2566 lines
78 KiB
C++
2566 lines
78 KiB
C++
/**
|
|
* ScriptTextEditor.cpp
|
|
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "ScriptTextEditor.h"
|
|
|
|
// Qt Headers
|
|
#include <QSettings>
|
|
#include <QTextCursor>
|
|
|
|
// Roblox Headers
|
|
#include "script/ScriptContext.h"
|
|
#include "v8datamodel/DataModel.h"
|
|
|
|
// Roblox Studio Headers
|
|
#include "RobloxScriptDoc.h"
|
|
#include "RobloxMainWindow.h"
|
|
#include "AuthoringSettings.h"
|
|
#include "ScriptSyntaxHighlighter.h"
|
|
#include "ScriptSideWidget.h"
|
|
#include "FindDialog.h"
|
|
#include "UpdateUIManager.h"
|
|
#include "QtUtilities.h"
|
|
#include "StudioIntellesense.h"
|
|
#include "RobloxDocManager.h"
|
|
#include "ScriptAnalysisWidget.h"
|
|
#include "RobloxCustomWidgets.h"
|
|
|
|
FASTFLAG(LuaDebugger)
|
|
FASTFLAG(StudioSeparateActionByActivationMethod)
|
|
|
|
FASTFLAGVARIABLE(ScriptContextSearchFix, true)
|
|
|
|
static const int MaxFontZoomIn = 30;
|
|
static const int MaxFontZoomOut = 5;
|
|
static const int WikiSearchTimerInterval = 1000;
|
|
ScriptTextEditor::WikiLookup ScriptTextEditor::wikiLookup = WikiLookup("");
|
|
|
|
FASTINTVARIABLE(StudioSyntaxCheckTimerInterval, 500)
|
|
|
|
FASTFLAGVARIABLE(ElseAutoIndentFix, false)
|
|
FASTFLAGVARIABLE(StudioEmbeddedFindDialogEnabled, false)
|
|
|
|
// required for QList<QTextEdit::ExtraSelection>
|
|
static bool operator== (const QTextEdit::ExtraSelection& lhs, const QTextEdit::ExtraSelection& rhs)
|
|
{ return (lhs.cursor == rhs.cursor && lhs.format == rhs.format); }
|
|
|
|
// TODO - auto-format selected text
|
|
|
|
/*****************************************************************************/
|
|
// ScriptTextEditor
|
|
/*****************************************************************************/
|
|
|
|
ScriptTextEditor::ScriptTextEditor(
|
|
RobloxScriptDoc* pScriptDoc,
|
|
RobloxMainWindow* pMainWindow)
|
|
: QPlainTextEdit(pMainWindow),
|
|
m_pScriptDoc(pScriptDoc),
|
|
m_pSyntaxHighlighter(NULL),
|
|
m_pSideWidget(NULL),
|
|
m_pCheckSyntaxThread(NULL),
|
|
m_pFindThread(NULL),
|
|
m_pCheckSyntaxTimer(NULL),
|
|
m_errorLine(-1),
|
|
m_pMainWindow(pMainWindow),
|
|
currentWikiSearchSelection(),
|
|
m_selectionMutex(QMutex::Recursive)
|
|
{
|
|
// highlighter
|
|
m_pSyntaxHighlighter = new ScriptSyntaxHighlighter(document());
|
|
|
|
m_pSideWidget = new ScriptSideWidget(this);
|
|
connect(this,SIGNAL(updateRequest(QRect,int)),m_pSideWidget,SLOT(updateArea()));
|
|
if (FFlag::LuaDebugger)
|
|
connect(m_pSideWidget, SIGNAL(toggleBreakpoint(int)), this, SIGNAL(toggleBreakpoint(int)));
|
|
|
|
m_pCheckSyntaxThread = new CheckSyntaxThread(this, pScriptDoc);
|
|
connect(this,SIGNAL(textChanged()),this,SLOT(checkSyntax()));
|
|
if (FFlag::StudioEmbeddedFindDialogEnabled)
|
|
{
|
|
m_pFindThread = new FindThread(this, pScriptDoc);
|
|
connect(this, SIGNAL(textChanged()), this, SLOT(startFind()));
|
|
}
|
|
|
|
m_pCheckSyntaxTimer = new QTimer(this);
|
|
connect(m_pCheckSyntaxTimer,SIGNAL(timeout()),this,SLOT(onCheckSyntaxTimerTimeOut()));
|
|
|
|
setFrameShape(QFrame::NoFrame);
|
|
setFrameShadow(QFrame::Plain);
|
|
setMouseTracking(true);
|
|
|
|
// always show scroll bars - avoids flickering
|
|
// (-- Not too sure when does this happen? disabling for ribbon)
|
|
if (!m_pMainWindow->isRibbonStyle())
|
|
{
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
}
|
|
|
|
// listen for changes to the editor settings
|
|
m_PropertyChangedConnection = AuthoringSettings::singleton().propertyChangedSignal.connect(
|
|
boost::bind(&ScriptTextEditor::onPropertyChanged,this,_1));
|
|
onPropertyChanged(NULL);
|
|
|
|
m_ContextMenu = new QMenu(this);
|
|
{
|
|
m_ContextMenu->addAction(m_pMainWindow->undoAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->redoAction);
|
|
|
|
m_ContextMenu->addSeparator();
|
|
|
|
m_ContextMenu->addAction(m_pMainWindow->cutAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->copyAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->pasteAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->deleteSelectedAction);
|
|
|
|
m_ContextMenu->addSeparator();
|
|
|
|
m_ContextMenu->addAction(m_pMainWindow->selectAllAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->goToScriptErrorAction);
|
|
|
|
m_ContextMenu->addSeparator();
|
|
m_ContextMenu->addAction(m_pMainWindow->commentSelectionAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->uncommentSelectionAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->toggleCommentAction);
|
|
|
|
m_ContextMenu->addSeparator();
|
|
m_ContextMenu->addAction(m_pMainWindow->expandAllFoldsAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->collapseAllFoldsAction);
|
|
|
|
m_ContextMenu->addSeparator();
|
|
m_ContextMenu->addAction(m_pMainWindow->zoomInAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->zoomOutAction);
|
|
m_ContextMenu->addAction(m_pMainWindow->resetScriptZoomAction);
|
|
}
|
|
|
|
connect(this, SIGNAL(selectionChanged()), this, SLOT(selectionChangedSearch()));
|
|
connect(this, SIGNAL(helpTopicChanged(const QString&)),
|
|
&RobloxContextualHelpService::singleton(), SLOT(onHelpTopicChanged(const QString&)));
|
|
|
|
m_pWikiSearchTimer = new QTimer(this);
|
|
m_pWikiSearchTimer->setSingleShot(true);
|
|
connect(m_pWikiSearchTimer, SIGNAL(timeout()), this, SLOT(onSearchTimeout()));
|
|
}
|
|
|
|
ScriptTextEditor::~ScriptTextEditor()
|
|
{
|
|
if (m_pCheckSyntaxThread)
|
|
{
|
|
delete m_pCheckSyntaxThread;
|
|
m_pCheckSyntaxThread = NULL;
|
|
}
|
|
|
|
delete m_pWikiSearchTimer;
|
|
|
|
m_PropertyChangedConnection.disconnect();
|
|
}
|
|
|
|
void ScriptTextEditor::contextMenuEvent(QContextMenuEvent* event)
|
|
{
|
|
UpdateUIManager::Instance().updateToolBars();
|
|
if (FFlag::LuaDebugger)
|
|
Q_EMIT updateContextualMenu(m_ContextMenu, event->pos());
|
|
m_ContextMenu->exec(event->globalPos());
|
|
}
|
|
|
|
void ScriptTextEditor::focusInEvent(QFocusEvent* evt)
|
|
{
|
|
QPlainTextEdit::focusInEvent(evt);
|
|
RobloxDocManager::Instance().setCurrentDoc(m_pScriptDoc);
|
|
if (AuthoringSettings::singleton().intellisenseEnabled)
|
|
connect(&Studio::Intellesense::singleton(), SIGNAL(doubleClickSignal(QListWidgetItem*)), this, SLOT(intellesenseDoubleClick(QListWidgetItem*)));
|
|
}
|
|
|
|
void ScriptTextEditor::focusOutEvent(QFocusEvent* evt)
|
|
{
|
|
QPlainTextEdit::focusOutEvent(evt);
|
|
|
|
if (m_pScriptDoc)
|
|
m_pScriptDoc->applyEditChanges();
|
|
if (AuthoringSettings::singleton().intellisenseEnabled)
|
|
disconnect(&Studio::Intellesense::singleton(), SIGNAL(doubleClickSignal(QListWidgetItem*)), this, SLOT(intellesenseDoubleClick(QListWidgetItem*)));
|
|
}
|
|
|
|
void ScriptTextEditor::intellesenseDoubleClick(QListWidgetItem* listItem)
|
|
{
|
|
QString blockText = document()->findBlockByNumber(textCursor().blockNumber()).text();
|
|
int cursorPos = textCursor().positionInBlock();
|
|
|
|
Studio::Intellesense::singleton().replaceTextWithCurrentItem(blockText, cursorPos);
|
|
replaceText(blockText, cursorPos);
|
|
}
|
|
|
|
void ScriptTextEditor::replaceText(QString text, int cursorPos)
|
|
{
|
|
//replace text with blockText
|
|
QTextCursor cursor = textCursor();
|
|
cursor.beginEditBlock();
|
|
cursor.movePosition(QTextCursor::EndOfBlock);
|
|
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
|
|
cursor.removeSelectedText();
|
|
cursor.insertText(text);
|
|
//Setting cursor position
|
|
cursor.setPosition(textCursor().position() - textCursor().positionInBlock() + cursorPos);
|
|
cursor.endEditBlock();
|
|
this->setTextCursor(cursor);
|
|
}
|
|
|
|
void ScriptTextEditor::setViewportMargins(
|
|
int left,
|
|
int top,
|
|
int right,
|
|
int bottom)
|
|
{
|
|
QPlainTextEdit::setViewportMargins(left,top,right,bottom);
|
|
}
|
|
|
|
QTextBlock ScriptTextEditor::firstVisibleBlock() const
|
|
{
|
|
return QPlainTextEdit::firstVisibleBlock();
|
|
}
|
|
|
|
QPointF ScriptTextEditor::contentOffset() const
|
|
{
|
|
return QPlainTextEdit::contentOffset();
|
|
}
|
|
|
|
QRectF ScriptTextEditor::blockBoundingGeometry(const QTextBlock& block) const
|
|
{
|
|
return QPlainTextEdit::blockBoundingGeometry(block);
|
|
}
|
|
|
|
void ScriptTextEditor::expandAllFolds(bool expand)
|
|
{
|
|
m_pSideWidget->toggleAllFolds(expand);
|
|
}
|
|
|
|
void ScriptTextEditor::resizeEvent(QResizeEvent* e)
|
|
{
|
|
QPlainTextEdit::resizeEvent(e);
|
|
m_pSideWidget->setFixedHeight(height());
|
|
if (FFlag::StudioEmbeddedFindDialogEnabled)
|
|
updateEmbeddedFindPosition();
|
|
}
|
|
|
|
void ScriptTextEditor::updateEmbeddedFindPosition()
|
|
{
|
|
if (FFlag::StudioEmbeddedFindDialogEnabled)
|
|
FindReplaceProvider::instance().getEmbeddedFindDialog()->updateFindPosition(mapToGlobal(viewport()->pos()), viewport()->size());
|
|
}
|
|
|
|
void ScriptTextEditor::homeEvent(QKeyEvent* e)
|
|
{
|
|
QTextCursor currentCursor = textCursor();
|
|
|
|
QString left = currentCursor.block().text().left(currentCursor.positionInBlock());
|
|
int moveOffset = left.indexOf(QRegExp("\\S"));
|
|
|
|
if (moveOffset < 0)
|
|
currentCursor.movePosition(QTextCursor::StartOfBlock, (e->modifiers() == Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
|
else
|
|
currentCursor.setPosition(currentCursor.block().position() + moveOffset, (e->modifiers() == Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
|
|
|
setTextCursor(currentCursor);
|
|
}
|
|
|
|
void ScriptTextEditor::endEvent(QKeyEvent* e)
|
|
{
|
|
QTextCursor currentCursor = textCursor();
|
|
|
|
QString right = currentCursor.block().text().right(currentCursor.block().text().length() - currentCursor.positionInBlock());
|
|
int moveOffset = right.lastIndexOf(QRegExp("\\S"));
|
|
|
|
if (moveOffset < 0)
|
|
currentCursor.movePosition(QTextCursor::EndOfBlock, (e->modifiers() == Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
|
else
|
|
currentCursor.setPosition(currentCursor.position() + moveOffset + 1, (e->modifiers() == Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
|
|
|
setTextCursor(currentCursor);
|
|
}
|
|
|
|
bool ScriptTextEditor::event(QEvent* e)
|
|
{
|
|
if (e->type() == QEvent::ToolTip)
|
|
{
|
|
processTooltip(static_cast<QHelpEvent*>(e));
|
|
return true;
|
|
}
|
|
else if (e->type() == QEvent::KeyPress)
|
|
{
|
|
if (static_cast<QKeyEvent*>(e)->modifiers().testFlag(Qt::ControlModifier) && (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Tab))
|
|
{
|
|
e->accept();
|
|
QCoreApplication::sendEvent(&UpdateUIManager::Instance().getMainWindow(), e);
|
|
return true;
|
|
}
|
|
}
|
|
else if (e->type() == EXTRASELECTION_UPDATE)
|
|
{
|
|
updateFindSelection();
|
|
}
|
|
return QPlainTextEdit::event(e);
|
|
}
|
|
|
|
void ScriptTextEditor::mousePressEvent(QMouseEvent *e)
|
|
{
|
|
QPlainTextEdit::mousePressEvent(e);
|
|
|
|
QTextCursor cursor = textCursor();
|
|
m_originalCursorPosition = cursor.position();
|
|
}
|
|
|
|
void ScriptTextEditor::keyPressEvent(QKeyEvent* e)
|
|
{
|
|
if (!e || !document() || isReadOnly())
|
|
return;
|
|
|
|
const int key = e->key();
|
|
|
|
Qt::KeyboardModifiers modifiers = e->modifiers();
|
|
|
|
if (AuthoringSettings::singleton().intellisenseEnabled && modifiers != Qt::ControlModifier)
|
|
{
|
|
//Check if there is selection and key is alphanumeric or special
|
|
if (textCursor().hasSelection() && key < Qt::Key_nobreakspace)
|
|
textCursor().removeSelectedText();
|
|
|
|
int blockNumber = textCursor().blockNumber();
|
|
QString blockText = document()->findBlockByNumber(blockNumber).text();
|
|
int cursorPos = textCursor().positionInBlock();
|
|
|
|
QRect cursorGeometry = cursorRect();
|
|
QPoint topLeftCursorPos = mapToGlobal(cursorGeometry.topLeft());
|
|
QFontMetrics fontMetrics = QFontMetrics(font());
|
|
|
|
int cursorHeight = cursorGeometry.height();
|
|
|
|
cursorGeometry.setX(topLeftCursorPos.x() + (fontMetrics.averageCharWidth() * 2) + 40);
|
|
|
|
#ifdef __APPLE__
|
|
cursorGeometry.setX(cursorGeometry.x() + 5);
|
|
#endif
|
|
|
|
cursorGeometry.setY(topLeftCursorPos.y());
|
|
|
|
cursorGeometry.setWidth(1);
|
|
cursorGeometry.setHeight(cursorHeight);
|
|
|
|
if (Studio::Intellesense::singleton().activate(blockText, blockNumber, cursorPos, e, cursorGeometry, false, this, m_analysisResult))
|
|
{
|
|
if (blockText != document()->findBlockByNumber(blockNumber).text())
|
|
replaceText(blockText, cursorPos);
|
|
|
|
return;
|
|
}
|
|
}
|
|
else if (key != Qt::Key_Control && modifiers == Qt::ControlModifier)
|
|
{
|
|
Studio::Intellesense::singleton().deactivate();
|
|
}
|
|
|
|
if ((key == Qt::Key_Tab) || (key == Qt::Key_Backtab))
|
|
{
|
|
if (tabSelection(e->key() != Qt::Key_Backtab))
|
|
{
|
|
e->accept();
|
|
return;
|
|
}
|
|
else if ( key == Qt::Key_Backtab )
|
|
{
|
|
int pos = textCursor().position() - 1;
|
|
QString text = toPlainText();
|
|
if ( pos > 0 && toPlainText()[pos] == '\t' )
|
|
{
|
|
textCursor().deletePreviousChar();
|
|
e->accept();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if (key == Qt::Key_Insert)
|
|
{
|
|
setOverwriteMode(!overwriteMode());
|
|
e->accept();
|
|
return;
|
|
}
|
|
else if (key == Qt::Key_Home && modifiers != Qt::ControlModifier )
|
|
{
|
|
homeEvent(e);
|
|
e->accept();
|
|
return;
|
|
}
|
|
else if (key == Qt::Key_End && modifiers != Qt::ControlModifier )
|
|
{
|
|
endEvent(e);
|
|
e->accept();
|
|
return;
|
|
}
|
|
else if ( key == Qt::Key_Return || key == Qt::Key_Backspace )
|
|
{
|
|
// Treat Shift+Return as only Return (DE5218, DE5215)
|
|
if ( key == Qt::Key_Return && modifiers == Qt::ShiftModifier )
|
|
e->setModifiers(Qt::NoModifier);
|
|
|
|
QTextCursor currentTextCursor = textCursor();
|
|
RBXASSERT(!currentTextCursor.isNull());
|
|
|
|
QTextBlock startBlock = currentTextCursor.block();
|
|
RBXASSERT(startBlock.isValid());
|
|
RBXASSERT(startBlock.userData());
|
|
|
|
RBXTextUserData& userData = static_cast<RBXTextUserData&>(*startBlock.userData());
|
|
QTextBlock endBlock = ScriptEditorUtils::findFoldBoundary(startBlock,false);
|
|
|
|
// if the block is folded and the block ending is valid, unfold if necessary
|
|
if ( (userData.getFoldState() > 0) &&
|
|
endBlock.isValid() &&
|
|
(startBlock != endBlock) &&
|
|
!endBlock.isVisible() )
|
|
{
|
|
// if cursor is at the end then just set it at the end of end block
|
|
if ( key == Qt::Key_Return &&
|
|
!currentTextCursor.hasSelection() &&
|
|
currentTextCursor.atBlockEnd() )
|
|
{
|
|
currentTextCursor.setPosition(endBlock.position() + endBlock.length() - 1);
|
|
setTextCursor(currentTextCursor);
|
|
}
|
|
|
|
const int lineNumber = startBlock.blockNumber() + 1;
|
|
m_pSideWidget->unfold(lineNumber);
|
|
e->accept();
|
|
return;
|
|
}
|
|
// if we didn't unfold then we should auto indent next line
|
|
else if ( key == Qt::Key_Return && AuthoringSettings::singleton().editorAutoIndent )
|
|
{
|
|
QPlainTextEdit::keyPressEvent(e);
|
|
autoIndent();
|
|
e->accept();
|
|
return;
|
|
}
|
|
}
|
|
else if (FFlag::LuaDebugger && key == Qt::Key_F9)
|
|
{
|
|
QTextBlock block = textCursor().block();
|
|
if (block.isValid())
|
|
{
|
|
//let debugger handle it
|
|
Q_EMIT toggleBreakpoint(block.blockNumber());
|
|
e->accept();
|
|
return;
|
|
}
|
|
}
|
|
|
|
QPlainTextEdit::keyPressEvent(e);
|
|
}
|
|
|
|
void ScriptTextEditor::wheelEvent(QWheelEvent* event)
|
|
{
|
|
// if mouse scroll wheel + control => zoom text
|
|
if ( event->modifiers() & Qt::ControlModifier )
|
|
{
|
|
if ( event->delta() > 0 )
|
|
zoom(1);
|
|
else if ( event->delta() < 0 )
|
|
zoom(-1);
|
|
|
|
m_pMainWindow->zoomInAction->setEnabled(AuthoringSettings::singleton().editorFont.pointSize() < MaxFontZoomIn);
|
|
m_pMainWindow->zoomOutAction->setEnabled(AuthoringSettings::singleton().editorFont.pointSize() > MaxFontZoomOut);
|
|
event->accept();
|
|
}
|
|
else
|
|
{
|
|
QPlainTextEdit::wheelEvent(event);
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::zoom(int delta, bool setDeltaAsValue)
|
|
{
|
|
QFont font = AuthoringSettings::singleton().editorFont;
|
|
int fontSize = setDeltaAsValue ? delta : qBound(MaxFontZoomOut, font.pointSize() + delta, MaxFontZoomIn);
|
|
font.setPointSize(fontSize);
|
|
AuthoringSettings::singleton().setEditorFont(font);
|
|
}
|
|
|
|
bool ScriptTextEditor::tabSelection(bool bPrepend)
|
|
{
|
|
bool eventAccepted = false;
|
|
|
|
QTextCursor cursor = textCursor();
|
|
|
|
if (!cursor.isNull() && cursor.hasSelection())
|
|
{
|
|
int posAnchor = cursor.anchor();
|
|
int pos = cursor.position();
|
|
bool bDownwardSelection = (posAnchor < pos);
|
|
|
|
int startPos = cursor.selectionStart();
|
|
int endPos = cursor.selectionEnd();
|
|
|
|
QTextBlock startBlock = document()->findBlock(startPos);
|
|
QTextBlock endBlock = document()->findBlock(endPos);
|
|
|
|
if (startBlock.blockNumber() != endBlock.blockNumber())
|
|
{
|
|
bool bEndInMid = (endPos > endBlock.position());
|
|
if (!bEndInMid)
|
|
endBlock = endBlock.previous();
|
|
|
|
QString modifiedText;
|
|
QTextBlock tmpBlock = startBlock;
|
|
while (tmpBlock.isValid() && tmpBlock.blockNumber() <= endBlock.blockNumber())
|
|
{
|
|
if (bPrepend)
|
|
{
|
|
modifiedText.append("\t");
|
|
modifiedText.append(tmpBlock.text());
|
|
}
|
|
else
|
|
{
|
|
QString tmpStr = tmpBlock.text();
|
|
if (tmpStr.startsWith("\t"))
|
|
{
|
|
tmpStr.remove(0,1);
|
|
}
|
|
else if (tmpStr.startsWith(" "))
|
|
{
|
|
for (int ii = 0 ; ii < AuthoringSettings::singleton().editorTabWidth ; ii++)
|
|
{
|
|
if (tmpStr.startsWith(" "))
|
|
tmpStr.remove(0,1);
|
|
}
|
|
}
|
|
modifiedText.append(tmpStr);
|
|
}
|
|
|
|
modifiedText.append("\n");
|
|
bool isLastBlock = (tmpBlock == document()->lastBlock());
|
|
if (isLastBlock || (!bDownwardSelection && bEndInMid && (tmpBlock.blockNumber() == endBlock.blockNumber())))
|
|
modifiedText.remove(modifiedText.length() - 1,1); // Remove the extra "\n"
|
|
|
|
if (isLastBlock)
|
|
break;
|
|
|
|
tmpBlock = tmpBlock.next();
|
|
}
|
|
|
|
if (!modifiedText.isEmpty())
|
|
{
|
|
replaceSelection(startPos,endPos,modifiedText);
|
|
eventAccepted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return eventAccepted;
|
|
}
|
|
|
|
void ScriptTextEditor::replaceSelection(
|
|
int startPos,
|
|
int endPos,
|
|
const QString& text)
|
|
{
|
|
if (text.isEmpty())
|
|
return;
|
|
|
|
QTextCursor cursor = textCursor();
|
|
const QTextBlock startBlock = document()->findBlock(startPos);
|
|
QTextBlock endBlock = document()->findBlock(endPos);
|
|
const int anchorPos = cursor.anchor();
|
|
const int currentPos = cursor.position();
|
|
const bool isDownwards = (anchorPos <= currentPos);
|
|
const bool isEndInMid = (endPos > endBlock.position());
|
|
bool isLastBlock = false;
|
|
|
|
if (!isEndInMid)
|
|
endBlock = endBlock.previous();
|
|
|
|
// Correct the selection which may be incomplete
|
|
if (isDownwards)
|
|
{
|
|
cursor.setPosition(anchorPos);
|
|
if (anchorPos > startBlock.position())
|
|
cursor.movePosition(QTextCursor::StartOfBlock,QTextCursor::MoveAnchor);
|
|
|
|
if (endBlock != document()->lastBlock())
|
|
{
|
|
if (currentPos > endBlock.position())
|
|
cursor.setPosition(endBlock.next().position(),QTextCursor::KeepAnchor);
|
|
else
|
|
cursor.setPosition(currentPos,QTextCursor::KeepAnchor);
|
|
|
|
cursor.movePosition(QTextCursor::StartOfBlock,QTextCursor::KeepAnchor);
|
|
}
|
|
else
|
|
{
|
|
cursor.setPosition(currentPos,QTextCursor::KeepAnchor);
|
|
cursor.movePosition(QTextCursor::EndOfBlock,QTextCursor::KeepAnchor);
|
|
isLastBlock = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cursor.setPosition(anchorPos);
|
|
if ((anchorPos > endBlock.position()) && isEndInMid)
|
|
cursor.movePosition(QTextCursor::EndOfBlock,QTextCursor::MoveAnchor);
|
|
|
|
cursor.setPosition(currentPos,QTextCursor::KeepAnchor);
|
|
if (currentPos > startBlock.position())
|
|
cursor.movePosition(QTextCursor::StartOfBlock,QTextCursor::KeepAnchor);
|
|
}
|
|
|
|
// Insert text and restore selection
|
|
int anchorBefore = cursor.anchor();
|
|
int posBefore = cursor.position();
|
|
cursor.insertText(text);
|
|
int posAfter = cursor.position();
|
|
|
|
if (isDownwards)
|
|
{
|
|
cursor.setPosition(anchorBefore,QTextCursor::MoveAnchor);
|
|
cursor.setPosition(isLastBlock ? posAfter : posAfter-1,QTextCursor::KeepAnchor);
|
|
}
|
|
else
|
|
{
|
|
cursor.setPosition(posAfter,QTextCursor::MoveAnchor);
|
|
cursor.setPosition(posBefore,QTextCursor::KeepAnchor);
|
|
}
|
|
|
|
setTextCursor(cursor);
|
|
}
|
|
|
|
/**
|
|
* Auto indents the next line based on the current block level.
|
|
* For regular lines, the previous line's tabs will be used.
|
|
* For ending blocks, one tab will be removed.
|
|
*/
|
|
void ScriptTextEditor::autoIndent()
|
|
{
|
|
const QString data = toPlainText();
|
|
QTextCursor cursor = textCursor();
|
|
const int cursorPosition = cursor.position();
|
|
const QTextBlock previousBlock = cursor.block().previous();
|
|
const int previousPosition = previousBlock.position();
|
|
QString indent;
|
|
|
|
if (previousBlock.isValid())
|
|
{
|
|
RBXTextUserData* pUserData = dynamic_cast<RBXTextUserData*>(previousBlock.userData());
|
|
if (pUserData)
|
|
{
|
|
const int foldState = pUserData->getFoldState();
|
|
|
|
const QString text = previousBlock.text().trimmed() + ' ';
|
|
const bool isElse = text.startsWith("else ") || text.startsWith("elseif ");
|
|
int requiredTabs = 0;
|
|
|
|
// if block is not closed/ended yet or special case else
|
|
if ((foldState < 0) || isElse)
|
|
{
|
|
if (isElse)
|
|
{
|
|
// start at previous block
|
|
QTextBlock block = previousBlock.previous();
|
|
int foldState = 0;
|
|
|
|
while (block.isValid())
|
|
{
|
|
const RBXTextUserData* userData = dynamic_cast<RBXTextUserData*>(block.userData());
|
|
if (userData)
|
|
{
|
|
// add current block's foldState
|
|
foldState += userData->getFoldState();
|
|
|
|
// if we have positive fold state
|
|
if (foldState > 0)
|
|
{
|
|
QString line = block.text();
|
|
QString trimmed = line.trimmed() + ' ';
|
|
if (trimmed.startsWith("if ") || (FFlag::ElseAutoIndentFix && trimmed.startsWith("if(")))
|
|
{
|
|
while (line[requiredTabs] == '\t')
|
|
++requiredTabs;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
block = block.previous();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
requiredTabs = ScriptEditorUtils::noOfStartingTabs(
|
|
ScriptEditorUtils::findFoldBoundary(previousBlock,true));
|
|
}
|
|
|
|
// make indent string
|
|
while (indent.length() < requiredTabs)
|
|
indent.append('\t');
|
|
|
|
// select the text to replace
|
|
cursor.setPosition(previousPosition,QTextCursor::MoveAnchor);
|
|
cursor.setPosition(cursorPosition,QTextCursor::KeepAnchor);
|
|
|
|
QString modifiedText = previousBlock.text();
|
|
|
|
// remove tabs at the beginning, we're going to set them correctly
|
|
while (modifiedText.startsWith('\t'))
|
|
modifiedText = modifiedText.remove(0,1);
|
|
|
|
modifiedText.prepend(indent); // add tabs at beginning
|
|
modifiedText.append('\n');
|
|
modifiedText.append(indent); // add tabs at end
|
|
|
|
if (isElse)
|
|
modifiedText.append('\t');
|
|
|
|
cursor.insertText(modifiedText);
|
|
cursor.clearSelection();
|
|
}
|
|
else
|
|
{
|
|
const int noOfOpeningTabs = ScriptEditorUtils::noOfStartingTabs(
|
|
ScriptEditorUtils::findFoldBoundary(previousBlock,true));
|
|
while (indent.length() < noOfOpeningTabs)
|
|
indent.append('\t');
|
|
|
|
// if we're opening a new block, indent one more
|
|
if (foldState > 0)
|
|
{
|
|
if (getDocFoldState() > 0)
|
|
{
|
|
autoCompleteBlock(text, indent);
|
|
return;
|
|
}
|
|
indent.append('\t');
|
|
}
|
|
|
|
cursor.insertText(indent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::autoCompleteBlock(const QString& text, QString& indent)
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
QString foldEnd = QString("end");
|
|
QRegExp commentMatchingExp = QRegExp("(--)?\\[=*\\[");
|
|
if (text.contains(QRegExp("\\brepeat\\b")) && !text.contains(QRegExp("\\buntil\\b")))
|
|
foldEnd = QString("until");
|
|
else if ((commentMatchingExp.indexIn(text) > -1) && !text.contains(commentMatchingExp.cap().replace('[', ']')))
|
|
foldEnd = QString(commentMatchingExp.cap().replace('[', ']'));
|
|
else if (text.contains("{") && !text.contains("}"))
|
|
foldEnd = QString("}");
|
|
else if (text.contains("(") && !text.contains(")"))
|
|
foldEnd = QString(")");
|
|
else if (text.contains("function") && text.count("(") > text.count(")"))
|
|
foldEnd = QString("end)");
|
|
|
|
indent.append(QString("\t\n") + indent + foldEnd);
|
|
cursor.insertText(indent);
|
|
cursor.movePosition(cursor.PreviousBlock);
|
|
cursor.movePosition(cursor.EndOfBlock);
|
|
setTextCursor(cursor);
|
|
}
|
|
|
|
void ScriptTextEditor::checkSyntax()
|
|
{
|
|
// Hide any visible tooltip
|
|
if (QToolTip::isVisible())
|
|
QToolTip::hideText();
|
|
|
|
if (m_pCheckSyntaxTimer)
|
|
m_pCheckSyntaxTimer->start(FInt::StudioSyntaxCheckTimerInterval);
|
|
else if (m_pCheckSyntaxThread)
|
|
QTimer::singleShot(0,this,SLOT(onCheckSyntax()));
|
|
}
|
|
|
|
void ScriptTextEditor::onCheckSyntax()
|
|
{
|
|
if (m_pCheckSyntaxThread)
|
|
m_pCheckSyntaxThread->requestRun();
|
|
}
|
|
|
|
void ScriptTextEditor::startFind()
|
|
{
|
|
if (QToolTip::isVisible())
|
|
QToolTip::hideText();
|
|
|
|
m_findSelectionFormat.setBackground(QBrush(QtUtilities::toQColor(AuthoringSettings::singleton().editorFindSelectionBackgroundColor)));
|
|
|
|
m_pFindThread->setAutoFindNext(false);
|
|
clearFoundItems();
|
|
|
|
QTimer::singleShot(0,this,SLOT(onFind()));
|
|
}
|
|
|
|
void ScriptTextEditor::onFind()
|
|
{
|
|
if (m_pFindThread)
|
|
m_pFindThread->requestRun();
|
|
}
|
|
|
|
/**
|
|
* Set the current error message.
|
|
* This method is invoked.
|
|
*
|
|
* @param errorLine line in text, 0 based
|
|
* @param errorMessage error text, must be copy because of multi-threading
|
|
*/
|
|
void ScriptTextEditor::setErrorLine(
|
|
int errorLine,
|
|
QString errorMessage)
|
|
{
|
|
// make sure we do not show error for invalid line
|
|
if (FFlag::LuaDebugger && errorLine < 0 && m_errorLine < 0)
|
|
return;
|
|
|
|
QList<QTextEdit::ExtraSelection> extraSelections;
|
|
|
|
QString message = errorMessage;
|
|
int visibleLine = errorLine;
|
|
|
|
QTextBlock errorBlock = document()->findBlockByNumber(visibleLine);
|
|
if ( errorBlock.isValid() )
|
|
{
|
|
// if block isn't visible, modify error message
|
|
if ( !errorBlock.isVisible() )
|
|
message = "Error in block: " + errorMessage;
|
|
|
|
// search up until we find a block that is actually visible
|
|
// this will always succeed if we hit the top level
|
|
while ( !errorBlock.isVisible() || errorBlock.text().length() == 0 )
|
|
errorBlock = ScriptEditorUtils::findFoldBoundary(errorBlock.previous(),true);
|
|
|
|
visibleLine = errorBlock.blockNumber();
|
|
|
|
// squiggly underline the bad line
|
|
QTextEdit::ExtraSelection selection;
|
|
selection.format = m_ErrorFormat;
|
|
selection.cursor = QTextCursor(errorBlock);
|
|
selection.cursor.clearSelection();
|
|
extraSelections.append(selection);
|
|
}
|
|
|
|
// if the block is invalid, this will clear any old error line
|
|
setExtraSelections(extraSelections);
|
|
|
|
// if this is a new error, report to status bar
|
|
if ( visibleLine >= 0 &&
|
|
( m_visibleErrorLine != visibleLine || m_errorMessage != message) )
|
|
{
|
|
m_pMainWindow->statusBar()->showMessage(message,5000);
|
|
}
|
|
|
|
m_errorMessage = message;
|
|
m_errorLine = errorLine;
|
|
m_visibleErrorLine = visibleLine;
|
|
|
|
m_pMainWindow->goToScriptErrorAction->setEnabled(m_errorLine >= 0);
|
|
}
|
|
|
|
void ScriptTextEditor::setAnalysisResult(const RBX::ScriptAnalyzer::Result& analysisResults)
|
|
{
|
|
m_scriptMessageForRange.clear();
|
|
m_errorLine = -1;
|
|
removeFromExtraSelections(m_AnalysisSelections);
|
|
|
|
QList<QTextEdit::ExtraSelection> extraSelections;
|
|
RBX::ScriptAnalyzer::Location loc;
|
|
int startPos = -1, endPos = -1;
|
|
bool atEof = false;
|
|
|
|
if (analysisResults.error)
|
|
{
|
|
RBX::ScriptAnalyzer::Error error = analysisResults.error.get();
|
|
loc = error.location;
|
|
|
|
populateValuesFromLocation(loc, startPos, endPos, atEof);
|
|
if (appendExtraSelection(m_ErrorFormat, startPos, endPos, atEof, m_AnalysisSelections))
|
|
{
|
|
// if this is a new error, report to status bar
|
|
QString message = error.text.c_str();
|
|
m_pMainWindow->statusBar()->showMessage(message, 5000);
|
|
|
|
// save error information (if we need to show tooltip for the entire error line, then set startPos and endPos as 0)
|
|
ScriptMessage sm = { 0, message, startPos, endPos };
|
|
m_scriptMessageForRange.insert(std::make_pair(std::make_pair(startPos, endPos), sm));
|
|
|
|
m_errorLine = loc.begin.line;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_analysisResult = analysisResults.intellesenseAnalysis;
|
|
}
|
|
|
|
for (size_t i = 0; i < analysisResults.warnings.size(); ++i)
|
|
{
|
|
RBX::ScriptAnalyzer::Warning warning = analysisResults.warnings[i];
|
|
loc = warning.location;
|
|
|
|
populateValuesFromLocation(loc, startPos, endPos, atEof);
|
|
if (appendExtraSelection(m_WarningFormat, startPos, endPos, atEof, m_AnalysisSelections))
|
|
{
|
|
QString message;
|
|
message.sprintf("W%03d: %s", warning.code, warning.text.c_str());
|
|
|
|
ScriptMessage sm = {warning.code, message, startPos, endPos};
|
|
m_scriptMessageForRange.insert(std::make_pair(std::make_pair(startPos, endPos), sm));
|
|
}
|
|
}
|
|
|
|
// update editor to refresh errors/warnings status
|
|
update();
|
|
|
|
// update goToError action state
|
|
m_pMainWindow->goToScriptErrorAction->setEnabled(m_errorLine >= 0);
|
|
// update analysis widget
|
|
ScriptAnalysisWidget& scriptAnalysis = UpdateUIManager::Instance().getViewWidget<ScriptAnalysisWidget>(eDW_SCRIPT_ANALYSIS);
|
|
scriptAnalysis.updateResults(m_pScriptDoc->getCurrentScript(), analysisResults);
|
|
}
|
|
|
|
bool ScriptTextEditor::appendExtraSelection(const QTextCharFormat& format, int startPos, int endPos, bool atEof, QList<QTextEdit::ExtraSelection>& appendList)
|
|
{
|
|
if (!atEof && ((startPos < 0) || (endPos < 0) || (endPos < startPos)))
|
|
return false;
|
|
|
|
QTextEdit::ExtraSelection selection;
|
|
selection.format = format;
|
|
selection.cursor = textCursor();
|
|
selection.cursor.clearSelection();
|
|
|
|
if (atEof)
|
|
{
|
|
selection.cursor.setPosition(startPos, QTextCursor::MoveAnchor);
|
|
selection.cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
|
selection.cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
|
}
|
|
else
|
|
{
|
|
selection.cursor.setPosition(startPos, QTextCursor::MoveAnchor);
|
|
selection.cursor.setPosition(endPos, QTextCursor::KeepAnchor);
|
|
}
|
|
|
|
addToExtraSelections(selection);
|
|
appendList.append(selection);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScriptTextEditor::processTooltip(QHelpEvent* evt)
|
|
{
|
|
if (!evt)
|
|
return;
|
|
|
|
// Assumption: showToopTip signal being used only from DebuggerClient so it should be safe to use return value of the signal
|
|
// we can have issues with this approach, if the same signal is connected to multiple slots.
|
|
bool handled = false;
|
|
if (FFlag::LuaDebugger)
|
|
handled = Q_EMIT showToolTip(evt->globalPos(), getVariableAtPos(evt->globalPos()));
|
|
|
|
if (handled || m_scriptMessageForRange.empty())
|
|
{
|
|
QToolTip::hideText();
|
|
evt->ignore();
|
|
return;
|
|
}
|
|
|
|
QPoint pos = viewport()->mapFromGlobal(evt->globalPos());
|
|
QTextCursor cursor = cursorForPosition(pos);
|
|
int cursorPos = cursor.position();
|
|
|
|
// multimap is required if we've overlaping range!
|
|
std::pair<MessageCollection::const_iterator, MessageCollection::const_iterator> ret = m_scriptMessageForRange.equal_range(Range(cursorPos, cursorPos));
|
|
if (ret.first != ret.second)
|
|
{
|
|
for (MessageCollection::const_iterator iter=ret.first; iter != ret.second; ++iter)
|
|
{
|
|
if ((cursorPos >= iter->first.first) && (cursorPos <= iter->first.second))
|
|
{
|
|
QToolTip::showText(evt->globalPos(), iter->second.text);
|
|
evt->accept();
|
|
handled = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!handled)
|
|
{
|
|
QToolTip::hideText();
|
|
evt->ignore();
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::commentSelection()
|
|
{
|
|
bool isBlockUnfolded = false;
|
|
updateSelection(&isBlockUnfolded);
|
|
|
|
QTextCursor cursor = textCursor();
|
|
if ( cursor.isNull() )
|
|
return;
|
|
|
|
const int startPos = cursor.selectionStart();
|
|
const int endPos = cursor.selectionEnd();
|
|
const QTextBlock startBlock = document()->findBlock(startPos);
|
|
QTextBlock endBlock = document()->findBlock(endPos);
|
|
const int anchorPos = cursor.anchor();
|
|
const int currentPos = cursor.position();
|
|
const bool isDownwards = (anchorPos <= currentPos);
|
|
|
|
bool isEndInMid = (endPos > endBlock.position());
|
|
|
|
if ( !isEndInMid )
|
|
endBlock = endBlock.previous();
|
|
|
|
QString modifiedText;
|
|
QTextBlock tmpBlock = startBlock;
|
|
|
|
while ( tmpBlock.isValid() && tmpBlock.blockNumber() <= endBlock.blockNumber() )
|
|
{
|
|
// check if we need to unfold the block
|
|
RBXTextUserData* userData = dynamic_cast<RBXTextUserData*>(tmpBlock.userData());
|
|
if ( userData && userData->getFoldState() && m_pSideWidget->isFolded(tmpBlock) )
|
|
{
|
|
const int lineNumber = tmpBlock.blockNumber() + 1;
|
|
m_pSideWidget->unfold(lineNumber, false);
|
|
isBlockUnfolded = true;
|
|
}
|
|
|
|
modifiedText.append("--");
|
|
modifiedText.append(tmpBlock.text());
|
|
modifiedText.append("\n");
|
|
|
|
bool isLastBlock = (tmpBlock == document()->lastBlock());
|
|
if ( isLastBlock ||
|
|
(!isDownwards && isEndInMid && (tmpBlock.blockNumber() == endBlock.blockNumber())))
|
|
{
|
|
modifiedText.remove(modifiedText.length() - 1,1); // Remove the extra "\n"
|
|
}
|
|
|
|
if ( isLastBlock )
|
|
break;
|
|
|
|
tmpBlock = tmpBlock.next();
|
|
}
|
|
|
|
replaceSelection(startPos,endPos,modifiedText);
|
|
|
|
// now update view
|
|
updateViewAndCursor(isBlockUnfolded);
|
|
}
|
|
|
|
void ScriptTextEditor::uncommentSelection()
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
if ( cursor.isNull() )
|
|
return;
|
|
|
|
const int startPos = cursor.selectionStart();
|
|
const int endPos = cursor.selectionEnd();
|
|
const QTextBlock startBlock = document()->findBlock(startPos);
|
|
QTextBlock endBlock = document()->findBlock(endPos);
|
|
const int anchorPos = cursor.anchor();
|
|
const int currentPos = cursor.position();
|
|
const bool isDownwards = (anchorPos <= currentPos);
|
|
|
|
bool isEndInMid = (endPos > endBlock.position());
|
|
if (!isEndInMid)
|
|
endBlock = endBlock.previous();
|
|
|
|
bool isBlockUnfolded = false;
|
|
QString modifiedText;
|
|
QTextBlock tmpBlock = startBlock;
|
|
while (tmpBlock.isValid() && tmpBlock.blockNumber() <= endBlock.blockNumber())
|
|
{
|
|
RBXTextUserData* userData = dynamic_cast<RBXTextUserData*>(tmpBlock.userData());
|
|
if ( userData && userData->getFoldState() && m_pSideWidget->isFolded(tmpBlock) )
|
|
{
|
|
const int lineNumber = tmpBlock.blockNumber() + 1;
|
|
m_pSideWidget->unfold(lineNumber, false);
|
|
isBlockUnfolded = true;
|
|
}
|
|
|
|
QString text = tmpBlock.text();
|
|
if (text.simplified().startsWith("--"))
|
|
text.remove(text.indexOf("--"),2);
|
|
modifiedText.append(text);
|
|
modifiedText.append("\n");
|
|
|
|
bool isLastBlock = (tmpBlock == document()->lastBlock());
|
|
if (isLastBlock ||
|
|
(!isDownwards && isEndInMid && (tmpBlock.blockNumber() == endBlock.blockNumber())))
|
|
{
|
|
modifiedText.remove(modifiedText.length() - 1,1); // Remove the extra "\n"
|
|
}
|
|
|
|
if (isLastBlock)
|
|
break;
|
|
|
|
tmpBlock = tmpBlock.next();
|
|
}
|
|
|
|
replaceSelection(startPos,endPos,modifiedText);
|
|
|
|
// now update view
|
|
updateViewAndCursor(isBlockUnfolded);
|
|
}
|
|
|
|
void ScriptTextEditor::toggleCommentSelection()
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
if ( cursor.isNull() )
|
|
return;
|
|
|
|
const int startPos = cursor.selectionStart();
|
|
const int endPos = cursor.selectionEnd();
|
|
const QTextBlock startBlock = document()->findBlock(startPos);
|
|
QTextBlock endBlock = document()->findBlock(endPos);
|
|
bool commented = true;
|
|
|
|
bool isEndInMid = (endPos > endBlock.position());
|
|
if (!isEndInMid)
|
|
endBlock = endBlock.previous();
|
|
|
|
QString modifiedText;
|
|
QTextBlock tmpBlock = startBlock;
|
|
while (tmpBlock.isValid() && tmpBlock.blockNumber() <= endBlock.blockNumber())
|
|
{
|
|
QString text = tmpBlock.text();
|
|
if (!text.startsWith("--"))
|
|
{
|
|
commented = false;
|
|
break;
|
|
}
|
|
|
|
bool isLastBlock = (tmpBlock == document()->lastBlock());
|
|
if (isLastBlock)
|
|
break;
|
|
|
|
tmpBlock = tmpBlock.next();
|
|
}
|
|
|
|
if (commented)
|
|
uncommentSelection();
|
|
else
|
|
commentSelection();
|
|
}
|
|
|
|
void ScriptTextEditor::updateSelection(bool* isBlockUnfolded)
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
if ( cursor.isNull() )
|
|
return;
|
|
|
|
int startPos = cursor.selectionStart();
|
|
int endPos = cursor.selectionEnd();
|
|
QTextBlock startBlock = document()->findBlock(startPos);
|
|
QTextBlock endBlock = document()->findBlock(endPos);
|
|
|
|
RBXTextUserData* userData = dynamic_cast<RBXTextUserData*>(startBlock.userData());
|
|
if ( userData && userData->getFoldState() )
|
|
{
|
|
if ( m_pSideWidget->isFolded(startBlock) )
|
|
{
|
|
const int lineNumber = startBlock.blockNumber() + 1;
|
|
m_pSideWidget->unfold(lineNumber, false);
|
|
if (isBlockUnfolded)
|
|
*isBlockUnfolded = true;
|
|
}
|
|
|
|
endBlock = ScriptEditorUtils::findFoldBoundary(endBlock,false);
|
|
if ( endBlock == document()->lastBlock() )
|
|
endPos = endBlock.position();
|
|
else
|
|
endPos = endBlock.next().position() - 1;
|
|
|
|
const bool isDownwards = (cursor.anchor() <= cursor.position());
|
|
if ( isDownwards )
|
|
{
|
|
cursor.setPosition(startPos,QTextCursor::MoveAnchor);
|
|
cursor.movePosition(QTextCursor::StartOfBlock,QTextCursor::MoveAnchor);
|
|
cursor.setPosition(endPos,QTextCursor::KeepAnchor);
|
|
cursor.movePosition(QTextCursor::EndOfBlock,QTextCursor::KeepAnchor);
|
|
}
|
|
else
|
|
{
|
|
cursor.setPosition(endPos,QTextCursor::MoveAnchor);
|
|
cursor.movePosition(QTextCursor::EndOfBlock,QTextCursor::MoveAnchor);
|
|
cursor.setPosition(startPos,QTextCursor::KeepAnchor);
|
|
cursor.movePosition(QTextCursor::StartOfBlock,QTextCursor::KeepAnchor);
|
|
}
|
|
|
|
setTextCursor(cursor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Goes to the last error line and ensures visible.
|
|
*/
|
|
void ScriptTextEditor::goToError()
|
|
{
|
|
if ( m_errorLine < 0 )
|
|
return;
|
|
|
|
QTextCursor cursor = textCursor();
|
|
QTextBlock errorBlock = document()->findBlockByNumber(m_errorLine);
|
|
cursor.setPosition(errorBlock.position());
|
|
setTextCursor(cursor);
|
|
|
|
ensureVisible(errorBlock);
|
|
}
|
|
|
|
/**
|
|
* Ensures the given block is visible by unfolding parents.
|
|
*/
|
|
void ScriptTextEditor::ensureVisible(const QTextBlock& block)
|
|
{
|
|
QTextBlock current = block;
|
|
while ( current.isValid() && !current.isVisible() )
|
|
{
|
|
QTextBlock parent = ScriptEditorUtils::findFoldBoundary(current.previous(),true);
|
|
m_pSideWidget->unfold(parent.blockNumber() + 1);
|
|
current = parent;
|
|
}
|
|
|
|
ensureCursorVisible();
|
|
updateFolds();
|
|
}
|
|
|
|
/**
|
|
* When updating due to fold changes we need to check syntax again.
|
|
*/
|
|
void ScriptTextEditor::updateFolds()
|
|
{
|
|
m_pCheckSyntaxThread->requestRun();
|
|
update();
|
|
}
|
|
|
|
void ScriptTextEditor::expandAllFolds()
|
|
{
|
|
expandAllFolds(true);
|
|
}
|
|
|
|
void ScriptTextEditor::collapseAllFolds()
|
|
{
|
|
expandAllFolds(false);
|
|
}
|
|
|
|
|
|
void ScriptTextEditor::goToLine()
|
|
{
|
|
FindReplaceProvider::instance().getGotoLineDialog()->activate(textCursor().blockNumber() + 1, document()->blockCount());
|
|
}
|
|
|
|
void ScriptTextEditor::find()
|
|
{
|
|
if (FFlag::StudioEmbeddedFindDialogEnabled)
|
|
{
|
|
FindReplaceProvider::instance().getEmbeddedFindDialog()->activate();
|
|
}
|
|
else
|
|
{
|
|
FindReplaceProvider::instance().getReplaceDialog()->hide();
|
|
FindReplaceProvider::instance().getFindDialog()->show();
|
|
FindReplaceProvider::instance().getFindDialog()->activateWindow();
|
|
FindReplaceProvider::instance().getFindDialog()->setFocus();
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::moveNext(bool forward)
|
|
{
|
|
QMutexLocker lock(&m_selectionMutex);
|
|
QTextCursor cursor = textCursor();
|
|
if (m_foundItems.size() > 0)
|
|
{
|
|
std::map<int, int>::iterator locationIter = forward ? m_foundItems.lower_bound(cursor.selectionEnd()) : m_foundItems.lower_bound(cursor.selectionStart());
|
|
if (forward)
|
|
{
|
|
if (locationIter != m_foundItems.end())
|
|
{
|
|
cursor.setPosition(locationIter->first, QTextCursor::MoveAnchor);
|
|
cursor.setPosition(locationIter->second, QTextCursor::KeepAnchor);
|
|
}
|
|
else
|
|
{
|
|
cursor.setPosition(m_foundItems.begin()->first, QTextCursor::MoveAnchor);
|
|
cursor.setPosition(m_foundItems.begin()->second, QTextCursor::KeepAnchor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::map<int, int>::reverse_iterator reverseLocationIter(locationIter);
|
|
|
|
if (reverseLocationIter != m_foundItems.rend())
|
|
{
|
|
cursor.setPosition(reverseLocationIter->first, QTextCursor::MoveAnchor);
|
|
cursor.setPosition(reverseLocationIter->second, QTextCursor::KeepAnchor);
|
|
}
|
|
else
|
|
{
|
|
cursor.setPosition(m_foundItems.rbegin()->first, QTextCursor::MoveAnchor);
|
|
cursor.setPosition(m_foundItems.rbegin()->second, QTextCursor::KeepAnchor);
|
|
}
|
|
}
|
|
|
|
this->setTextCursor(cursor);
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::replaceNext(const QRegExp& rx, const QString& text)
|
|
{
|
|
QMutexLocker lock(&m_selectionMutex);
|
|
if (m_foundItems.size() == 0)
|
|
return;
|
|
|
|
QTextCursor cursor = textCursor();
|
|
|
|
int startPos = cursor.selectionStart();
|
|
std::map<int, int>::iterator locationIter = m_foundItems.lower_bound(startPos);
|
|
|
|
if (locationIter == m_foundItems.end())
|
|
locationIter = m_foundItems.begin();
|
|
|
|
cursor.beginEditBlock();
|
|
cursor.setPosition(locationIter->first, QTextCursor::MoveAnchor);
|
|
cursor.setPosition(locationIter->second, QTextCursor::KeepAnchor);
|
|
QString updatedString = cursor.selectedText().replace(rx, text);
|
|
cursor.removeSelectedText();
|
|
cursor.insertText(updatedString);
|
|
cursor.endEditBlock();
|
|
|
|
moveNext(true);
|
|
}
|
|
|
|
void ScriptTextEditor::replaceAll(const QRegExp& rx, const QString& text)
|
|
{
|
|
QMutexLocker lock(&m_selectionMutex);
|
|
if (m_foundItems.size() == 0)
|
|
return;
|
|
|
|
QTextCursor cursor = textCursor();
|
|
|
|
cursor.beginEditBlock();
|
|
blockSignals(true);
|
|
|
|
|
|
for (std::map<int, int>::reverse_iterator iter = m_foundItems.rbegin(); iter != m_foundItems.rend(); ++iter)
|
|
{
|
|
cursor.setPosition(iter->first, QTextCursor::MoveAnchor);
|
|
cursor.setPosition(iter->second, QTextCursor::KeepAnchor);
|
|
QString updatedString = cursor.selectedText().replace(rx, text);
|
|
cursor.removeSelectedText();
|
|
cursor.insertText(updatedString);
|
|
}
|
|
|
|
blockSignals(false);
|
|
cursor.endEditBlock();
|
|
|
|
startFind();
|
|
}
|
|
|
|
void ScriptTextEditor::findNext(bool moveCursorToBeginningOfSelection)
|
|
{
|
|
if (FFlag::StudioEmbeddedFindDialogEnabled)
|
|
{
|
|
if (moveCursorToBeginningOfSelection)
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
cursor.setPosition(cursor.selectionStart());
|
|
setTextCursor(cursor);
|
|
}
|
|
|
|
moveNext(true);
|
|
}
|
|
else
|
|
{
|
|
FindReplaceProvider::instance().find(true);
|
|
if (!FindReplaceProvider::instance().getReplaceDialog()->isVisible() &&
|
|
!FindReplaceProvider::instance().getFindDialog()->isVisible() &&
|
|
!isActiveWindow() )
|
|
{
|
|
activateWindow();
|
|
setFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::findPrevious()
|
|
{
|
|
if (FFlag::StudioEmbeddedFindDialogEnabled)
|
|
{
|
|
moveNext(false);
|
|
}
|
|
else
|
|
{
|
|
FindReplaceProvider::instance().find(false);
|
|
if ( !FindReplaceProvider::instance().getReplaceDialog()->isVisible() &&
|
|
!FindReplaceProvider::instance().getFindDialog()->isVisible() &&
|
|
!isActiveWindow() )
|
|
{
|
|
activateWindow();
|
|
setFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::replace()
|
|
{
|
|
if (FFlag::StudioEmbeddedFindDialogEnabled)
|
|
{
|
|
FindReplaceProvider::instance().getEmbeddedFindDialog()->activate(true);
|
|
}
|
|
else
|
|
{
|
|
FindReplaceProvider::instance().getFindDialog()->hide();
|
|
FindReplaceProvider::instance().getReplaceDialog()->show();
|
|
FindReplaceProvider::instance().getReplaceDialog()->activateWindow();
|
|
FindReplaceProvider::instance().getReplaceDialog()->setFocus();
|
|
}
|
|
}
|
|
|
|
bool ScriptTextEditor::doHandleAction(const QString& actionID)
|
|
{
|
|
bool handled = true;
|
|
|
|
QString ignoredActions = "undoAction redoAction copyAction cutAction pasteAction ";
|
|
if (!FFlag::StudioSeparateActionByActivationMethod && ignoredActions.contains(actionID) )
|
|
return handled;
|
|
|
|
if (actionID == "undoAction")
|
|
undo();
|
|
else if (actionID == "redoAction")
|
|
redo();
|
|
else if (actionID == "copyAction")
|
|
copy();
|
|
else if (actionID == "cutAction")
|
|
cut();
|
|
else if (actionID == "pasteAction")
|
|
paste();
|
|
else if ( actionID == "findAction" )
|
|
find();
|
|
else if ( actionID == "replaceAction" )
|
|
replace();
|
|
else if ( actionID == "findNextAction" )
|
|
findNext();
|
|
else if ( actionID == "findPreviousAction" )
|
|
findPrevious();
|
|
else if ( actionID == "goToScriptErrorAction" )
|
|
goToError();
|
|
else if ( actionID == "goToLineAction" )
|
|
goToLine();
|
|
else if ( actionID == "commentSelectionAction" )
|
|
commentSelection();
|
|
else if ( actionID == "uncommentSelectionAction" )
|
|
uncommentSelection();
|
|
else if ( actionID == "toggleCommentAction" )
|
|
toggleCommentSelection();
|
|
else if ( actionID == "expandAllFoldsAction" )
|
|
expandAllFolds();
|
|
else if ( actionID == "collapseAllFoldsAction" )
|
|
collapseAllFolds();
|
|
else if ( actionID == "deleteSelectedAction" )
|
|
textCursor().removeSelectedText();
|
|
else if ( actionID == "selectAllAction" )
|
|
selectAll();
|
|
else if ( actionID == "zoomInAction" )
|
|
zoom(1);
|
|
else if ( actionID == "zoomOutAction" )
|
|
zoom(-1);
|
|
else if ( actionID == "resetScriptZoomAction" )
|
|
#ifdef Q_WS_MAC
|
|
zoom(14, true);
|
|
#else
|
|
zoom(10, true);
|
|
#endif
|
|
else
|
|
handled = false;
|
|
|
|
return handled;
|
|
}
|
|
|
|
bool ScriptTextEditor::isPasteEnabled()
|
|
{
|
|
QClipboard *pClipboard = QApplication::clipboard();
|
|
if (pClipboard)
|
|
{
|
|
const QMimeData *pMimeData = pClipboard->mimeData();
|
|
if (pMimeData && pMimeData->hasFormat("text/plain"))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ScriptTextEditor::actionState(const QString& actionID,bool& enableState,bool& checkedState)
|
|
{
|
|
if (isReadOnly())
|
|
{
|
|
// disable actions which can edit script code
|
|
static QString editableActionList = "undoAction redoAction pasteAction cutAction copyAction commentSelectionAction uncommentSelectionAction "
|
|
"deleteSelectedAction selectAllAction toggleCommentAction "
|
|
"simulationRunAction simulationPlayAction simulationStopAction simulationResetAction ";
|
|
|
|
if (editableActionList.contains(actionID))
|
|
{
|
|
enableState = false;
|
|
checkedState = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static QString disableActionList = "groupSelectionAction ungroupSelectionAction pasteIntoAction";
|
|
if (disableActionList.contains(actionID))
|
|
{
|
|
enableState = false;
|
|
checkedState = false;
|
|
return true;
|
|
}
|
|
|
|
if ( actionID == "findNextAction" || actionID == "findPreviousAction" )
|
|
enableState = FFlag::StudioEmbeddedFindDialogEnabled || FindReplaceProvider::instance().isFindNextEnabled();
|
|
else if ( actionID == "goToScriptErrorAction" )
|
|
enableState = m_errorLine >= 0;
|
|
else if ( actionID == "undoAction" )
|
|
enableState = document()->isUndoAvailable();
|
|
else if ( actionID == "redoAction" )
|
|
enableState = document()->isRedoAvailable();
|
|
else if ( actionID == "pasteAction" )
|
|
enableState = isPasteEnabled();
|
|
else if ( actionID == "selectAllAction" )
|
|
enableState = true;
|
|
else if ( actionID == "zoomInAction" )
|
|
enableState = AuthoringSettings::singleton().editorFont.pointSize() < MaxFontZoomIn;
|
|
else if ( actionID == "zoomOutAction" )
|
|
enableState = AuthoringSettings::singleton().editorFont.pointSize() > MaxFontZoomOut;
|
|
else
|
|
{
|
|
static QString requireSelectionActions =
|
|
"cutAction copyAction commentSelectionAction uncommentSelectionAction deleteSelectedAction";
|
|
if ( requireSelectionActions.contains(actionID) )
|
|
enableState = textCursor().hasSelection();
|
|
else
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScriptTextEditor::onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor)
|
|
{
|
|
bool updateText = false;
|
|
|
|
// if there is a color change, update the syntax colors
|
|
if ( !pDescriptor || pDescriptor->category.str == sScriptColorsCategoryName )
|
|
{
|
|
updateText = true;
|
|
|
|
// update viewport palette before setting stylesheet or else cursor color will not be changed
|
|
QPalette pt = viewport()->palette();
|
|
pt.setColor(QPalette::Text, QtUtilities::toQColor(AuthoringSettings::singleton().editorTextColor));
|
|
viewport()->setPalette(pt);
|
|
|
|
m_pSyntaxHighlighter->onPropertyChanged(pDescriptor);
|
|
setStyleSheet(constructStyleSheet());
|
|
// update error format background color
|
|
QColor errorColor = QtUtilities::toQColor(AuthoringSettings::singleton().editorErrorColor);
|
|
|
|
QColor warningColor = QtUtilities::toQColor(AuthoringSettings::singleton().editorWarningColor);
|
|
// first update selection
|
|
QList<QTextEdit::ExtraSelection> currentSelections = extraSelections();
|
|
for (int ii = 0; ii < currentSelections.size(); ++ii)
|
|
{
|
|
if (currentSelections[ii].format == m_ErrorFormat)
|
|
{
|
|
currentSelections[ii].format.setUnderlineColor(errorColor);
|
|
}
|
|
else if (currentSelections[ii].format == m_WarningFormat)
|
|
{
|
|
currentSelections[ii].format.setUnderlineColor(warningColor);
|
|
}
|
|
}
|
|
setExtraSelections(currentSelections);
|
|
// now update member variables
|
|
|
|
m_ErrorFormat.setUnderlineColor(errorColor);
|
|
|
|
m_WarningFormat.setUnderlineColor(warningColor);
|
|
}
|
|
|
|
if ( !pDescriptor || pDescriptor->category.str == sScriptCategoryName )
|
|
{
|
|
m_pSideWidget->onPropertyChanged(pDescriptor);
|
|
|
|
updateText = true;
|
|
|
|
// set the new font, make sure it's fixed pitch
|
|
QFont font = AuthoringSettings::singleton().editorFont;
|
|
font.setFixedPitch(true);
|
|
font.setUnderline(false);
|
|
font.setStrikeOut(false);
|
|
font.setBold(false);
|
|
setFont(font);
|
|
|
|
m_pSideWidget->onPropertyChanged(pDescriptor);
|
|
|
|
// adjust size of tabs based on font width and size of tab stop
|
|
const QFontMetrics metrics(font);
|
|
const int spaceWidth = metrics.width(' ');
|
|
setTabStopWidth(AuthoringSettings::singleton().editorTabWidth * spaceWidth);
|
|
|
|
if ( AuthoringSettings::singleton().editorTextWrap )
|
|
setLineWrapMode(QPlainTextEdit::WidgetWidth);
|
|
else
|
|
setLineWrapMode(QPlainTextEdit::NoWrap);
|
|
|
|
// adjust error text color
|
|
m_ErrorFormat.setFont(font);
|
|
m_ErrorFormat.setProperty(QTextFormat::FullWidthSelection,true);
|
|
m_ErrorFormat.setFontUnderline(true);
|
|
m_ErrorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
|
|
|
|
// adjust warning text
|
|
m_WarningFormat.setFont(font);
|
|
m_WarningFormat.setProperty(QTextFormat::FullWidthSelection,true);
|
|
m_WarningFormat.setFontUnderline(true);
|
|
m_WarningFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
|
|
}
|
|
|
|
// clear and reset the text to change colors and formating
|
|
if ( updateText )
|
|
{
|
|
m_pSyntaxHighlighter->setFont(font());
|
|
m_pSyntaxHighlighter->rehighlight();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a character is part of a valid word.
|
|
* Characters that are not alpha numeric or an underscore break up words.
|
|
*
|
|
* @param c character to check
|
|
* @return true if the character is part of a word
|
|
*/
|
|
bool ScriptTextEditor::isWordCharacter(const QChar& c)
|
|
{
|
|
if ( c == '_' || c.isLetterOrNumber() )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Finds a regular expression in a document text block.
|
|
*
|
|
* @param block block to search
|
|
* @param expression regular expression to search for
|
|
* @param offset offset in block
|
|
* @param option search options
|
|
* @param cursor output cursor
|
|
* @return true if found
|
|
*/
|
|
bool ScriptTextEditor::findInBlock(
|
|
const QTextBlock& block,
|
|
const QRegExp& expression,
|
|
int offset,
|
|
QTextDocument::FindFlags options,
|
|
QTextCursor& cursor )
|
|
{
|
|
const QRegExp expr(expression);
|
|
QString text = block.text();
|
|
text.replace(QChar::Nbsp,QLatin1Char(' '));
|
|
|
|
int idx = -1;
|
|
while ( offset >=0 && offset <= text.length() )
|
|
{
|
|
// move to next character index
|
|
idx = (options & QTextDocument::FindBackward) ? expr.lastIndexIn(text,offset) : expr.indexIn(text,offset);
|
|
if ( idx == -1 )
|
|
return false;
|
|
|
|
if ( options & QTextDocument::FindWholeWords )
|
|
{
|
|
// check for word breaks
|
|
const int start = idx;
|
|
const int end = start + expr.matchedLength();
|
|
if ( (start != 0 && isWordCharacter(text.at(start - 1))) ||
|
|
(end != text.length() && isWordCharacter(text.at(end))) )
|
|
{
|
|
//if this is not a whole word, continue the search in the string
|
|
offset = (options & QTextDocument::FindBackward) ? idx - 1 : end + 1;
|
|
idx = -1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//we have a hit, return the cursor for that.
|
|
break;
|
|
}
|
|
|
|
if ( idx == -1 )
|
|
return false;
|
|
|
|
// generate output cursor
|
|
cursor = QTextCursor(block.docHandle(),block.position() + idx);
|
|
cursor.setPosition(cursor.position() + expr.matchedLength(),QTextCursor::KeepAnchor);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Finds a regular expression in a text document.
|
|
*
|
|
* @param doc document to search
|
|
* @param expr regular expression
|
|
* @param from starting cursor location
|
|
* @param option search options
|
|
* @return cursor if found
|
|
*/
|
|
QTextCursor ScriptTextEditor::findInDoc(
|
|
const QTextDocument& doc,
|
|
const QRegExp& expr,
|
|
const QTextCursor& from,
|
|
QTextDocument::FindFlags options )
|
|
{
|
|
if ( expr.isEmpty() )
|
|
return QTextCursor();
|
|
|
|
int pos = 0;
|
|
if ( !from.isNull() )
|
|
{
|
|
// skip over current selection
|
|
if ( options & QTextDocument::FindBackward )
|
|
pos = from.selectionStart();
|
|
else
|
|
pos = from.selectionEnd();
|
|
}
|
|
|
|
// the cursor is positioned between characters, so for a backward search
|
|
// do not include the character given in the position.
|
|
if ( options & QTextDocument::FindBackward )
|
|
{
|
|
--pos ;
|
|
if ( pos < 0 )
|
|
return QTextCursor();
|
|
}
|
|
|
|
QTextCursor cursor;
|
|
QTextBlock block = doc.findBlock(pos);
|
|
|
|
if ( options & QTextDocument::FindBackward )
|
|
{
|
|
// search backwards
|
|
int blockOffset = pos - block.position();
|
|
while ( block.isValid() )
|
|
{
|
|
if ( findInBlock(block,expr,blockOffset,options,cursor) )
|
|
return cursor;
|
|
block = block.previous();
|
|
blockOffset = block.length() - 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// search forwards
|
|
int blockOffset = qMax(0, pos - block.position());
|
|
while ( block.isValid() )
|
|
{
|
|
if ( findInBlock(block,expr,blockOffset,options,cursor) )
|
|
return cursor;
|
|
blockOffset = 0;
|
|
block = block.next();
|
|
}
|
|
}
|
|
|
|
return QTextCursor();
|
|
}
|
|
|
|
bool ScriptTextEditor::find(bool loop,const QRegExp& regExp,QTextDocument::FindFlags flags)
|
|
{
|
|
if (FFlag::StudioEmbeddedFindDialogEnabled)
|
|
{
|
|
clearFoundItems();
|
|
m_pFindThread->setAutoFindNext(true);
|
|
m_pFindThread->setData(regExp, flags);
|
|
m_pFindThread->requestRun();
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
QTextCursor cursor = findInDoc(*document(),regExp,textCursor(),flags);
|
|
if ( cursor.isNull() && loop )
|
|
{
|
|
cursor = textCursor();
|
|
if ( flags & QTextDocument::FindBackward )
|
|
{
|
|
UpdateUIManager::Instance().getMainWindow().statusBar()->showMessage("Search wrapped to end",2000);
|
|
cursor.movePosition(QTextCursor::End);
|
|
}
|
|
else
|
|
{
|
|
UpdateUIManager::Instance().getMainWindow().statusBar()->showMessage("Search wrapped to start",2000);
|
|
cursor.movePosition(QTextCursor::Start);
|
|
}
|
|
|
|
cursor = findInDoc(*document(),regExp,cursor,flags);
|
|
}
|
|
|
|
if ( !cursor.isNull() )
|
|
{
|
|
setTextCursor(cursor);
|
|
ensureVisible(cursor.block());
|
|
}
|
|
|
|
return !cursor.isNull();
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::replace(const QString& text,const QRegExp& regExp,QTextDocument::FindFlags flags)
|
|
{
|
|
textCursor().insertText(text);
|
|
}
|
|
|
|
QString ScriptTextEditor::getSelectedText()
|
|
{
|
|
return textCursor().selectedText();
|
|
}
|
|
|
|
bool ScriptTextEditor::hasSelection()
|
|
{
|
|
return textCursor().hasSelection();
|
|
}
|
|
|
|
void ScriptTextEditor::goToStart(QTextDocument::FindFlags flags)
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
|
|
// The start of the search will be the end of the document when we
|
|
// are searching backwards.
|
|
if ( flags & QTextDocument::FindBackward )
|
|
{
|
|
cursor.movePosition(QTextCursor::End);
|
|
}
|
|
else
|
|
{
|
|
cursor.movePosition(QTextCursor::Start);
|
|
}
|
|
|
|
setTextCursor(cursor);
|
|
}
|
|
|
|
int ScriptTextEditor::getDocFoldState(int blockNumber)
|
|
{
|
|
int foldCount = 0;
|
|
QTextBlock iterBlock = document()->findBlockByLineNumber(0);
|
|
|
|
if (!blockNumber)
|
|
blockNumber = document()->blockCount();
|
|
|
|
while (iterBlock.isValid() && iterBlock.blockNumber() <= blockNumber)
|
|
{
|
|
RBXTextUserData* pUserData = dynamic_cast<RBXTextUserData*>(iterBlock.userData());
|
|
if (pUserData)
|
|
foldCount += pUserData->getFoldState();
|
|
iterBlock = iterBlock.next();
|
|
}
|
|
return foldCount;
|
|
}
|
|
|
|
QString ScriptTextEditor::constructStyleSheet()
|
|
{
|
|
QString styleSheet;
|
|
|
|
// set style sheet so we can change the text color, background color,
|
|
// selected text color, and selected background color
|
|
styleSheet = QString(STRINGIFY(
|
|
QPlainTextEdit
|
|
{
|
|
color: %1;
|
|
background: %2;
|
|
selection-color: %3;
|
|
selection-background-color: %4;
|
|
} )).
|
|
arg(QtUtilities::toQColor(AuthoringSettings::singleton().editorTextColor).name()).
|
|
arg(QtUtilities::toQColor(AuthoringSettings::singleton().editorBackgroundColor).name()).
|
|
arg(QtUtilities::toQColor(AuthoringSettings::singleton().editorSelectionColor).name()).
|
|
arg(QtUtilities::toQColor(AuthoringSettings::singleton().editorSelectionBackgroundColor).name());
|
|
|
|
return styleSheet;
|
|
}
|
|
|
|
QString ScriptTextEditor::getVariableAtPos(const QPoint& globalPos)
|
|
{
|
|
QPoint pos = viewport()->mapFromGlobal(globalPos);
|
|
QTextCursor cursor = cursorForPosition(pos);
|
|
if (cursor.block().isValid())
|
|
{
|
|
// get the cursor position before selecting the block text
|
|
int cursorPos = cursor.position() - cursor.block().position();
|
|
// select the entire block
|
|
cursor.select(QTextCursor::BlockUnderCursor);
|
|
// get block rect
|
|
QRect blockRect = cursorRect(cursor);
|
|
blockRect.setLeft(0);
|
|
// if mouse position is inside the block rect then only proceed
|
|
if (blockRect.contains(pos))
|
|
{
|
|
// get block text under the cursor
|
|
QString blockText = cursor.selectedText();
|
|
if (cursorPos > 0 && cursorPos < blockText.size() && blockText.at(cursorPos).isLetterOrNumber())
|
|
{
|
|
int startPos = variableStartPos(blockText, cursorPos);
|
|
int endPos = variableEndPos(blockText, cursorPos);
|
|
if (endPos > startPos)
|
|
return blockText.mid(startPos, endPos - startPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
int ScriptTextEditor::variableStartPos(const QString &text, int startPos)
|
|
{
|
|
// variable in Lua can have letter, number, underscore and member variables can be accessed via dot
|
|
for ( ; startPos >= 0; startPos--)
|
|
{
|
|
int tempPos = startPos;
|
|
if (startPos > 0 && text.at(startPos) == '.')
|
|
tempPos = startPos-1;
|
|
|
|
if (!isWordCharacter(text.at(tempPos)))
|
|
return startPos+1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ScriptTextEditor::variableEndPos(const QString &text, int endPos)
|
|
{
|
|
for ( ; endPos < text.size(); endPos++)
|
|
{
|
|
if (!isWordCharacter(text.at(endPos)))
|
|
return endPos;
|
|
}
|
|
|
|
return text.size();
|
|
}
|
|
|
|
void ScriptTextEditor::moveCursorToLine(int line)
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
if (document()->blockCount() < line)
|
|
return;
|
|
|
|
int position = document()->findBlockByNumber(line).position();
|
|
|
|
cursor.setPosition(position);
|
|
|
|
setTextCursor(cursor);
|
|
ensureVisible(cursor.block());
|
|
}
|
|
|
|
void ScriptTextEditor::selectText(int startPosition, int endPosition)
|
|
{
|
|
QTextCursor cursor = textCursor();
|
|
cursor.setPosition(startPosition);
|
|
cursor.setPosition(endPosition, QTextCursor::KeepAnchor);
|
|
setTextCursor(cursor);
|
|
ensureVisible(cursor.block());
|
|
}
|
|
|
|
void ScriptTextEditor::selectText(const RBX::ScriptAnalyzer::Location& location, bool trySettingBlocksVisible)
|
|
{
|
|
int startPos = getStartPosition(location);
|
|
int endPos = getEndPosition(location);
|
|
|
|
if (startPos >= 0 && endPos >= 0 && (endPos >= startPos))
|
|
{
|
|
selectText(startPos, endPos);
|
|
}
|
|
else if (trySettingBlocksVisible)
|
|
{
|
|
// if we couldn't get valid start and end pos, then try to unfold blocks
|
|
QTextBlock startBlock = document()->findBlockByNumber(location.begin.line);
|
|
if (startBlock.isValid() && !startBlock.isVisible())
|
|
ensureVisible(startBlock);
|
|
QTextBlock endBlock = document()->findBlockByNumber(location.end.line);
|
|
if (endBlock.isValid() && !endBlock.isVisible())
|
|
ensureVisible(endBlock);
|
|
// again try to select text
|
|
selectText(location, false);
|
|
}
|
|
|
|
centerCursor();
|
|
}
|
|
|
|
bool ScriptTextEditor::addToExtraSelections(const QTextEdit::ExtraSelection& extraSelection, bool prepend)
|
|
{
|
|
QList<QTextEdit::ExtraSelection> currentSelections = extraSelections();
|
|
if (currentSelections.contains(extraSelection))
|
|
return false;
|
|
|
|
prepend ? currentSelections.push_front(extraSelection) : currentSelections.append(extraSelection);
|
|
setExtraSelections(currentSelections);
|
|
return true;
|
|
}
|
|
|
|
void ScriptTextEditor::removeFromExtraSelections(QList<QTextEdit::ExtraSelection> selectionsToRemove)
|
|
{
|
|
QList<QTextEdit::ExtraSelection> currentSelections = extraSelections();
|
|
for (int ii = 0; ii < selectionsToRemove.size(); ++ii)
|
|
currentSelections.removeOne(selectionsToRemove.at(ii));
|
|
setExtraSelections(currentSelections);
|
|
}
|
|
|
|
void ScriptTextEditor::updateViewAndCursor(bool resizeRequired)
|
|
{
|
|
// if block was unfolded, then we will need to resize the viewport
|
|
if ( resizeRequired )
|
|
{
|
|
viewport()->resize(viewport()->sizeHint());
|
|
// viewport gets updated only after a draw call so delay cursor visibility
|
|
// if we don't, then incorrect scrolling will happen
|
|
QTimer::singleShot(0,this,SLOT(updateCursorVisibility()));
|
|
}
|
|
else
|
|
{
|
|
// directly update cursor
|
|
updateCursorVisibility();
|
|
}
|
|
|
|
// initiate syntax check
|
|
updateFolds();
|
|
}
|
|
|
|
void ScriptTextEditor::updateCursorVisibility()
|
|
{ ensureCursorVisible(); }
|
|
|
|
void ScriptTextEditor::updateWikiLookup(const char* key, std::string value)
|
|
{
|
|
std::pair<int, std::string> lookupPair = wikiLookup[key];
|
|
lookupPair.first++;
|
|
if(lookupPair.first > 1)
|
|
lookupPair.second = "";
|
|
else
|
|
lookupPair.second = value;
|
|
wikiLookup[key] = lookupPair;
|
|
}
|
|
|
|
void ScriptTextEditor::setupWikiLookup()
|
|
{
|
|
for(RBX::Reflection::ClassDescriptor::ClassDescriptors::const_iterator iter = RBX::Reflection::ClassDescriptor::all_begin();
|
|
iter!=RBX::Reflection::ClassDescriptor::all_end();++iter)
|
|
{
|
|
std::string className = ((*iter)->name).toString();
|
|
updateWikiLookup((*iter)->name.c_str(), "API:Class/" + className);
|
|
|
|
for(RBX::Reflection::MemberDescriptorContainer<RBX::Reflection::PropertyDescriptor>::Collection::const_iterator propIter = (*iter)->begin<RBX::Reflection::PropertyDescriptor>();
|
|
propIter!=(*iter)->end<RBX::Reflection::PropertyDescriptor>();++propIter)
|
|
if(className.compare((*propIter)->owner.name.toString()) == 0)
|
|
updateWikiLookup((*propIter)->name.c_str(), ("API:Class/" + className + "/" + (*propIter)->name.toString()).c_str());
|
|
|
|
for(RBX::Reflection::MemberDescriptorContainer<RBX::Reflection::FunctionDescriptor>::Collection::const_iterator funcIter = (*iter)->begin<RBX::Reflection::FunctionDescriptor>();
|
|
funcIter!=(*iter)->end<RBX::Reflection::FunctionDescriptor>();++funcIter)
|
|
if(className.compare((*funcIter)->owner.name.toString()) == 0)
|
|
updateWikiLookup((*funcIter)->name.c_str(), ("API:Class/" + className + "/" + (*funcIter)->name.toString()).c_str());
|
|
|
|
for(RBX::Reflection::MemberDescriptorContainer<RBX::Reflection::YieldFunctionDescriptor>::Collection::const_iterator yfuncIter = (*iter)->begin<RBX::Reflection::YieldFunctionDescriptor>();
|
|
yfuncIter!=(*iter)->end<RBX::Reflection::YieldFunctionDescriptor>();++yfuncIter)
|
|
if(className.compare((*yfuncIter)->owner.name.toString()) == 0)
|
|
updateWikiLookup((*yfuncIter)->name.c_str(), ("API:Class/" + className + "/" + (*yfuncIter)->name.toString()).c_str());
|
|
|
|
for(RBX::Reflection::MemberDescriptorContainer<RBX::Reflection::EventDescriptor>::Collection::const_iterator eventIter = (*iter)->begin<RBX::Reflection::EventDescriptor>();
|
|
eventIter!=(*iter)->end<RBX::Reflection::EventDescriptor>();++eventIter)
|
|
if(className.compare((*eventIter)->owner.name.toString()) == 0)
|
|
updateWikiLookup((*eventIter)->name.c_str(), ("API:Class/" + className + "/" + (*eventIter)->name.toString()).c_str());
|
|
|
|
for(RBX::Reflection::MemberDescriptorContainer<RBX::Reflection::CallbackDescriptor>::Collection::const_iterator callbackIter = (*iter)->begin<RBX::Reflection::CallbackDescriptor>();
|
|
callbackIter!=(*iter)->end<RBX::Reflection::CallbackDescriptor>();++callbackIter)
|
|
if(className.compare((*callbackIter)->owner.name.toString()) == 0)
|
|
updateWikiLookup((*callbackIter)->name.c_str(), ("API:Class/" + className + "/" + (*callbackIter)->name.toString()).c_str());
|
|
}
|
|
|
|
for(std::vector< const RBX::Reflection::EnumDescriptor* >::const_iterator enumIter = RBX::Reflection::EnumDescriptor::enumsBegin();
|
|
enumIter!=RBX::Reflection::EnumDescriptor::enumsEnd();++enumIter)
|
|
{
|
|
std::string enumName = (*enumIter)->name.toString();
|
|
updateWikiLookup((*enumIter)->name.c_str(), "API:Enum/" + enumName);
|
|
|
|
for(std::vector< const RBX::Reflection::EnumDescriptor::Item* >::const_iterator itemItor = (*enumIter)->begin();
|
|
itemItor!=(*enumIter)->end(); ++itemItor)
|
|
{
|
|
std::string itemName = (*itemItor)->name.toString();
|
|
updateWikiLookup((*itemItor)->name.c_str(), "API:Enum/" + enumName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::selectionChangedSearch()
|
|
{
|
|
if (textCursor().hasSelection())
|
|
{
|
|
QString selection = textCursor().selectedText();
|
|
if(selection.isEmpty())
|
|
m_pWikiSearchTimer->stop();
|
|
else
|
|
{
|
|
if (!m_pWikiSearchTimer->isActive())
|
|
{
|
|
m_pWikiSearchTimer->start(WikiSearchTimerInterval);
|
|
currentWikiSearchSelection = selection;
|
|
}
|
|
else if (QString::compare(selection, currentWikiSearchSelection) != 0)
|
|
{
|
|
m_pWikiSearchTimer->start(WikiSearchTimerInterval);
|
|
currentWikiSearchSelection = selection;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::onSearchTimeout()
|
|
{
|
|
if (!currentWikiSearchSelection.isEmpty())
|
|
{
|
|
QString searchTerm = currentWikiSearchSelection;
|
|
std::pair<int, std::string> lookupPair = wikiLookup[searchTerm.toStdString().c_str()];
|
|
|
|
if (lookupPair.first == 1)
|
|
searchTerm = QString(lookupPair.second.c_str());
|
|
else
|
|
{
|
|
// Don't search for special characters!
|
|
if (FFlag::ScriptContextSearchFix && searchTerm.contains(QRegExp("[^0-9a-zA-Z]")))
|
|
return;
|
|
|
|
searchTerm.prepend("Special:Search?search=");
|
|
searchTerm.append("&fulltext=Search&studiomode=true");
|
|
}
|
|
|
|
Q_EMIT(helpTopicChanged(searchTerm));
|
|
}
|
|
}
|
|
|
|
void ScriptTextEditor::populateValuesFromLocation(const RBX::ScriptAnalyzer::Location& location, int& startPos, int& endPos, bool& atEof)
|
|
{
|
|
atEof = (location.begin.line + 1 == (unsigned int)document()->lineCount() && (location.begin.column == location.end.column));
|
|
if (!atEof)
|
|
{
|
|
startPos = getStartPosition(location);
|
|
endPos = getEndPosition(location);
|
|
}
|
|
else
|
|
{
|
|
QTextBlock beginBlock = document()->findBlockByLineNumber(location.begin.line);
|
|
startPos = beginBlock.position();
|
|
endPos = startPos + beginBlock.length();
|
|
}
|
|
}
|
|
|
|
int ScriptTextEditor::getStartPosition(const RBX::ScriptAnalyzer::Location& location)
|
|
{
|
|
QTextBlock beginBlock = document()->findBlockByNumber(location.begin.line);
|
|
if (beginBlock.isValid() && beginBlock.isVisible())
|
|
return beginBlock.position() + location.begin.column;
|
|
return -1;
|
|
}
|
|
|
|
int ScriptTextEditor::getEndPosition(const RBX::ScriptAnalyzer::Location& location)
|
|
{
|
|
QTextBlock endBlock = document()->findBlockByNumber(location.end.line);
|
|
if (endBlock.isValid() && endBlock.isVisible())
|
|
return endBlock.position() + location.end.column;
|
|
return -1;
|
|
}
|
|
|
|
void ScriptTextEditor::onCheckSyntaxTimerTimeOut()
|
|
{
|
|
m_pCheckSyntaxTimer->stop();
|
|
onCheckSyntax();
|
|
}
|
|
|
|
void ScriptTextEditor::addFoundItem(int start, int end)
|
|
{
|
|
QMutexLocker lock(&m_selectionMutex);
|
|
m_foundItems.insert(std::pair<int, int>(start, end));
|
|
|
|
std::map<int,int>::iterator firstAfter = m_extraSelectionsToAdd.lower_bound(start);
|
|
|
|
if (m_extraSelectionsToAdd.size() > 0 && firstAfter != m_extraSelectionsToAdd.begin())
|
|
{
|
|
--firstAfter;
|
|
|
|
int previousStart = firstAfter->first;
|
|
int previousEnd = firstAfter->second;
|
|
|
|
if (start <= previousEnd && end >= previousStart)
|
|
{
|
|
start = qMin(start, previousStart);
|
|
end = qMax(end, previousEnd);
|
|
|
|
m_extraSelectionsToAdd.erase(firstAfter);
|
|
}
|
|
}
|
|
|
|
m_extraSelectionsToAdd.insert(std::pair<int, int>(start, end));
|
|
}
|
|
|
|
void ScriptTextEditor::clearFoundItems()
|
|
{
|
|
QMutexLocker lock(&m_selectionMutex);
|
|
|
|
m_foundItems.clear();
|
|
|
|
for (QList<QTextEdit::ExtraSelection>::iterator iter = m_currentFoundItems.begin(); iter != m_currentFoundItems.end(); ++iter)
|
|
m_extraSelectionsToRemove.insert(std::pair<int, QTextEdit::ExtraSelection>(iter->cursor.selectionStart(), *iter));
|
|
|
|
m_extraSelectionsToAdd.clear();
|
|
m_currentFoundItems.clear();
|
|
QApplication::postEvent(this, new RobloxCustomEvent(EXTRASELECTION_UPDATE));
|
|
}
|
|
|
|
void ScriptTextEditor::updateFindSelection()
|
|
{
|
|
QApplication::removePostedEvents(this, EXTRASELECTION_UPDATE);
|
|
|
|
RBX::Time timeCheck = RBX::Time::now<RBX::Time::Fast>();
|
|
|
|
bool selectionsUpdated = false;
|
|
|
|
QTextEdit::ExtraSelection selection;
|
|
selection.cursor = textCursor();
|
|
|
|
int firstVisibleBlockPosition = firstVisibleBlock().position();
|
|
QMutexLocker lock(&m_selectionMutex);
|
|
|
|
while ((RBX::Time::now<RBX::Time::Fast>() - timeCheck).msec() < 10)
|
|
{
|
|
if (m_extraSelectionsToAdd.empty())
|
|
break;
|
|
|
|
std::map<int, int>::iterator selectionToMake = m_extraSelectionsToAdd.lower_bound(firstVisibleBlockPosition);
|
|
|
|
if (selectionToMake == m_extraSelectionsToAdd.end())
|
|
selectionToMake = m_extraSelectionsToAdd.begin();
|
|
|
|
selection.format = m_findSelectionFormat;
|
|
|
|
|
|
selection.cursor.setPosition(selectionToMake->first, QTextCursor::MoveAnchor);
|
|
selection.cursor.setPosition(selectionToMake->second, QTextCursor::KeepAnchor);
|
|
|
|
m_currentSelections.append(selection);
|
|
m_currentFoundItems.append(selection);
|
|
|
|
m_extraSelectionsToAdd.erase(selectionToMake);
|
|
}
|
|
|
|
timeCheck = RBX::Time::now<RBX::Time::Fast>();
|
|
|
|
while ((RBX::Time::now<RBX::Time::Fast>() - timeCheck).msec() < 10)
|
|
{
|
|
for (int i = 0; i < 100; ++i)
|
|
{
|
|
if (m_extraSelectionsToRemove.empty())
|
|
break;
|
|
|
|
std::multimap<int, QTextEdit::ExtraSelection>::iterator selectionToRemove = m_extraSelectionsToRemove.lower_bound(firstVisibleBlockPosition);
|
|
|
|
if (selectionToRemove == m_extraSelectionsToRemove.end())
|
|
selectionToRemove = m_extraSelectionsToRemove.begin();
|
|
|
|
m_currentSelections.removeOne(selectionToRemove->second);
|
|
|
|
m_extraSelectionsToRemove.erase(selectionToRemove);
|
|
}
|
|
|
|
setExtraSelections(m_currentSelections);
|
|
|
|
if (m_extraSelectionsToRemove.empty())
|
|
break;
|
|
}
|
|
|
|
|
|
if (!m_extraSelectionsToAdd.empty() || !m_extraSelectionsToRemove.empty())
|
|
QApplication::postEvent(this, new RobloxCustomEvent(EXTRASELECTION_UPDATE));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
// ScriptTraversalThread
|
|
/*****************************************************************************/
|
|
|
|
ScriptTraversalThread::ScriptTraversalThread(ScriptTextEditor *parent, RobloxScriptDoc *document)
|
|
:QThread(parent),
|
|
m_pScriptEditor(parent),
|
|
m_pDocument(document),
|
|
restart(false),
|
|
abort(false)
|
|
{}
|
|
|
|
ScriptTraversalThread::~ScriptTraversalThread()
|
|
{
|
|
mutex.lock();
|
|
abort = true;
|
|
condition.wakeOne();
|
|
mutex.unlock();
|
|
|
|
wait();
|
|
}
|
|
|
|
void ScriptTraversalThread::requestRun()
|
|
{
|
|
if (!isRunning())
|
|
{
|
|
start(IdlePriority);
|
|
}
|
|
else
|
|
{
|
|
restart = true;
|
|
condition.wakeOne();
|
|
}
|
|
}
|
|
|
|
void ScriptTraversalThread::run()
|
|
{
|
|
while (true)
|
|
{
|
|
if (abort)
|
|
return;
|
|
|
|
doWork();
|
|
|
|
if (!restart)
|
|
condition.wait(&mutex);
|
|
|
|
restart = false;
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
// CheckSyntaxThread
|
|
/*****************************************************************************/
|
|
|
|
void CheckSyntaxThread::doWork()
|
|
{
|
|
shared_ptr<RBX::DataModel> dm = m_pDocument->getDataModel();
|
|
|
|
void* resultPtr = NULL;
|
|
|
|
{
|
|
RBX::DataModel::LegacyLock l(dm.get(), RBX::DataModelJob::Write);
|
|
|
|
std::string code = m_pScriptEditor->toPlainText().toStdString();
|
|
shared_ptr<RBX::Instance> scriptInstance = m_pDocument->getCurrentScript().toInstance();
|
|
// Linked script source won't have an instance, so use a fake one to appease the analyzer
|
|
if (!scriptInstance)
|
|
{
|
|
scriptInstance = RBX::Creatable<RBX::Instance>::create<RBX::Script>();
|
|
}
|
|
|
|
RBX::ScriptAnalyzer::Result result = RBX::ScriptAnalyzer::analyze(dm.get(), scriptInstance, code);
|
|
QMetaObject::invokeMethod(m_pScriptEditor, "setAnalysisResult", Qt::QueuedConnection, Q_ARG(RBX::ScriptAnalyzer::Result, result));
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
// FindThread
|
|
/*****************************************************************************/
|
|
|
|
void FindThread::setData(const QRegExp& regExp,QTextDocument::FindFlags flags)
|
|
{
|
|
restart = true;
|
|
|
|
toSearch = regExp;
|
|
searchFlags = flags;
|
|
}
|
|
|
|
bool FindThread::findInBlockNoCursor(
|
|
const QString& text,
|
|
int startOfBlock,
|
|
const QRegExp& expression,
|
|
int offset,
|
|
QTextDocument::FindFlags options,
|
|
int& start,
|
|
int& end)
|
|
|
|
{
|
|
const QRegExp expr(expression);
|
|
|
|
int idx = -1;
|
|
while ( offset >=0 && offset <= text.length() )
|
|
{
|
|
// move to next character index
|
|
idx = (options & QTextDocument::FindBackward) ? expr.lastIndexIn(text,offset) : expr.indexIn(text,offset);
|
|
if ( idx == -1 )
|
|
return false;
|
|
|
|
if ( options & QTextDocument::FindWholeWords )
|
|
{
|
|
// check for word breaks
|
|
const int start = idx;
|
|
const int end = start + expr.matchedLength();
|
|
if ( (start != 0 && ScriptTextEditor::isWordCharacter(text.at(start - 1))) ||
|
|
(end != text.length() && ScriptTextEditor::isWordCharacter(text.at(end))) )
|
|
{
|
|
//if this is not a whole word, continue the search in the string
|
|
offset = end + 1;
|
|
idx = -1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//we have a hit, return the cursor for that.
|
|
break;
|
|
}
|
|
|
|
if ( idx == -1 )
|
|
return false;
|
|
|
|
// generate output cursor
|
|
start = startOfBlock + idx;
|
|
end = start + expr.matchedLength();
|
|
return true;
|
|
}
|
|
|
|
void FindThread::doWork()
|
|
{
|
|
m_pScriptEditor->clearFoundItems();
|
|
|
|
if (toSearch.isEmpty())
|
|
{
|
|
QMetaObject::invokeMethod(FindReplaceProvider::instance().getEmbeddedFindDialog(), "setNumberOfFoundResults", Q_ARG(int, -1));
|
|
QMetaObject::invokeMethod(m_pScriptEditor, "onCheckSyntax");
|
|
return;
|
|
}
|
|
|
|
shared_ptr<RBX::DataModel> dm = m_pDocument->getDataModel();
|
|
|
|
RBX::DataModel::LegacyLock lock(dm.get(), RBX::DataModelJob::Write);
|
|
|
|
QTextBlock block;
|
|
|
|
int offset = 0;
|
|
block = m_pScriptEditor->firstVisibleBlock();
|
|
m_searchStartBlockLine = block.blockNumber();
|
|
m_searchLastBlockLine = -1;
|
|
|
|
int start, end;
|
|
QString blockText = block.text();
|
|
int blockStart = block.position();
|
|
|
|
RBX::Time timeCheck = RBX::Time::now<RBX::Time::Fast>();
|
|
|
|
bool hasFoundItem = false;
|
|
|
|
while (m_searchStartBlockLine != m_searchLastBlockLine)
|
|
{
|
|
{
|
|
QMutexLocker lock(&mutex);
|
|
if (restart || abort)
|
|
return;
|
|
}
|
|
|
|
if (findInBlockNoCursor(blockText, blockStart, toSearch, offset, searchFlags, start, end))
|
|
{
|
|
offset = start - block.position() + 1;
|
|
m_pScriptEditor->addFoundItem(start, end);
|
|
hasFoundItem = true;
|
|
}
|
|
else
|
|
{
|
|
offset = 0;
|
|
|
|
blockStart = blockStart + block.length();
|
|
|
|
block = block.next();
|
|
if (!block.isValid())
|
|
{
|
|
block = m_pScriptEditor->document()->firstBlock();
|
|
blockStart = 0;
|
|
}
|
|
|
|
blockText = block.text();
|
|
blockText.replace(QChar::Nbsp,QLatin1Char(' '));
|
|
|
|
m_searchLastBlockLine = block.blockNumber();
|
|
}
|
|
|
|
if (hasFoundItem && (RBX::Time::now<RBX::Time::Fast>() - timeCheck).msec() > 20)
|
|
{
|
|
timeCheck = RBX::Time::now<RBX::Time::Fast>();
|
|
hasFoundItem = false;
|
|
QApplication::postEvent(m_pScriptEditor, new RobloxCustomEvent(EXTRASELECTION_UPDATE));
|
|
}
|
|
}
|
|
|
|
if (hasFoundItem)
|
|
QApplication::postEvent(m_pScriptEditor, new RobloxCustomEvent(EXTRASELECTION_UPDATE));
|
|
|
|
QMetaObject::invokeMethod(FindReplaceProvider::instance().getEmbeddedFindDialog(), "setNumberOfFoundResults", Q_ARG(int, m_pScriptEditor->numberOfFoundItems()));
|
|
|
|
QMetaObject::invokeMethod(m_pScriptEditor, "onCheckSyntax");
|
|
if (FindReplaceProvider::instance().getEmbeddedFindDialog()->hasFocus())
|
|
QMetaObject::invokeMethod(m_pScriptEditor, "findNext", Q_ARG(bool, true));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
// ScriptEditorUtils
|
|
/*****************************************************************************/
|
|
|
|
QTextBlock ScriptEditorUtils::findFoldBoundary(
|
|
QTextBlock endBlock,
|
|
bool bUp)
|
|
{
|
|
QTextBlock blockFound = endBlock;
|
|
int nFolds = 0;
|
|
|
|
while (blockFound.isValid())
|
|
{
|
|
RBXTextUserData* pUserData = dynamic_cast<RBXTextUserData*>(blockFound.userData());
|
|
if (pUserData)
|
|
nFolds += pUserData->getFoldState();
|
|
|
|
if (bUp)
|
|
{
|
|
if (nFolds >= 0)
|
|
break;
|
|
|
|
blockFound = blockFound.previous();
|
|
}
|
|
else
|
|
{
|
|
if (nFolds <= 0)
|
|
break;
|
|
|
|
blockFound = blockFound.next();
|
|
}
|
|
}
|
|
|
|
return blockFound;
|
|
}
|
|
|
|
int ScriptEditorUtils::noOfStartingTabs(QTextBlock block)
|
|
{
|
|
if (!block.isValid())
|
|
return -1;
|
|
|
|
int nTabs = 0;
|
|
QString text = block.text();
|
|
while (text.mid(nTabs,1) == "\t")
|
|
{
|
|
nTabs++;
|
|
}
|
|
|
|
return nTabs;
|
|
}
|