diff --git a/libnymea-app/scripting/codecompletion.cpp b/libnymea-app/scripting/codecompletion.cpp index 0c04f37c..17b0e308 100644 --- a/libnymea-app/scripting/codecompletion.cpp +++ b/libnymea-app/scripting/codecompletion.cpp @@ -427,28 +427,40 @@ void CodeCompletion::update() QString id = blockText; id.remove(QRegExp(".* ")).remove(QRegExp("\\.[a-zA-Z0-9]*")); QString type = getIdTypes().value(id); + int blockPosition = getBlockPosition(id); + BlockInfo blockInfo = getBlockInfo(blockPosition); + qDebug() << "dot expression:" << id << type; - // Classes + qDebug() << "lvalue info:" << blockInfo.properties.keys() << blockInfo.properties.value("actionName") << blockInfo.properties.value("actionTypeId"); + + // Append properties of the type foreach (const QString &property, m_classes.value(type).properties) { entries.append(CompletionModel::Entry(property, property, "property")); } + // Append user-defined properties of the type + foreach (const QString &property, blockInfo.properties.keys()) { + CompletionModel::Entry entry(property, property, "property"); + if (!entries.contains(entry)) { + entries.append(entry); + } + } + + // Append methods of the item foreach (const QString &method, m_classes.value(type).methods) { QString paramString; + // If it's an execute() call, also autocomplete the params if (method == "execute") { - int blockposition = getBlockPosition(id); - qDebug() << "Blockposition:" << blockposition; - if (blockposition >= 0) { - BlockInfo info = getBlockInfo(blockposition); - qDebug() << "actionType" << info.properties.keys() << info.properties.value("actionName") << info.properties.value("actionTypeId"); - if (info.valid) { - QString thingId = info.properties.value("thingId"); + qDebug() << "Blockposition:" << blockPosition; + if (blockPosition >= 0) { + if (blockInfo.valid) { + QString thingId = blockInfo.properties.value("thingId"); Device *d = m_engine->thingManager()->things()->getDevice(QUuid(thingId)); if (d) { ActionType *at = nullptr; - if (info.properties.contains("actionTypeId")) { - at = d->thingClass()->actionTypes()->getActionType(info.properties.value("actionTypeId")); - } else if (info.properties.contains("actionName")) { - at = d->thingClass()->actionTypes()->findByName(info.properties.value("actionName")); + if (blockInfo.properties.contains("actionTypeId")) { + at = d->thingClass()->actionTypes()->getActionType(blockInfo.properties.value("actionTypeId")); + } else if (blockInfo.properties.contains("actionName")) { + at = d->thingClass()->actionTypes()->findByName(blockInfo.properties.value("actionName")); } if (at) { QStringList params; @@ -466,6 +478,10 @@ void CodeCompletion::update() } entries.append(CompletionModel::Entry(method + "(", method, "method", "", paramString + ")")); } + // User-defined functions of the item + foreach (const QString &function, blockInfo.functions) { + entries.append(CompletionModel::Entry(function + "(", function, "method", "", ")")); + } // Attached classes/properties foreach (const QString &property, m_attachedClasses.value(id).properties) { entries.append(CompletionModel::Entry(property, property, "property")); @@ -595,8 +611,9 @@ CodeCompletion::BlockInfo CodeCompletion::getBlockInfo(int position) const } info.start = blockStart.position(); - info.end = blockEnd.position(); //m_document->textDocument()->find("}", position).position(); + info.end = blockEnd.position(); info.valid = true; +// qDebug() << "Block start:" << info.start << "end:" << info.end; info.name = blockStart.block().text(); info.name.remove(QRegExp(" *\\{ *")); @@ -607,14 +624,11 @@ CodeCompletion::BlockInfo CodeCompletion::getBlockInfo(int position) const int childBlocks = 0; while (!blockStart.isNull() && blockStart.position() < info.end) { QString line = blockStart.block().text(); - if (line.endsWith("{")) { +// qDebug() << "line:" << line; + if (line.contains("{") && !line.contains("}")) { childBlocks++; - if (!blockStart.movePosition(QTextCursor::NextBlock)) { - break; - } - continue; } - if (line.trimmed().startsWith("}")) { + if (line.contains("}") && !line.contains("{")) { childBlocks--; if (!blockStart.movePosition(QTextCursor::NextBlock)) { break; @@ -622,18 +636,30 @@ CodeCompletion::BlockInfo CodeCompletion::getBlockInfo(int position) const continue; } // \n - if (childBlocks > 1) { // Skip all stuff in child blocks + if (childBlocks > 1 && !(childBlocks == 2 && line.trimmed().startsWith("function"))) { // Skip all stuff in child blocks +// qDebug() << "skipping line in child block" << childBlocks; blockStart.movePosition(QTextCursor::NextBlock); continue; } foreach (const QString &statement, blockStart.block().text().split(";")) { +// qDebug() << "analysing statement:" << statement; QStringList parts = statement.split(":"); - if (parts.length() != 2) { - continue; + if (parts.length() == 2) { // Properties must be "foo: bar" + QString propName = parts.first().trimmed(); + if (propName.split(" ").count() > 1) { // trim modifiers e.g. "property bool foo: bar" + propName = propName.split(" ").last(); + } + if (propName.contains(".")) { // skip attached properties e.g. "Component.onCompleted: ..." + continue; + } + QString propValue = parts.last().split("//").first().trimmed().remove("\""); + info.properties.insert(propName, propValue); + } + parts = statement.trimmed().split(" "); + if (parts.count() >= 2 && parts.first().trimmed() == "function") { + QString functionHeader = parts.at(1).trimmed(); + info.functions.append(functionHeader.split("(").first()); } - QString propName = parts.first().trimmed(); - QString propValue = parts.last().split("//").first().trimmed().remove("\""); - info.properties.insert(propName, propValue); } if (!blockStart.movePosition(QTextCursor::NextBlock)) { break; diff --git a/libnymea-app/scripting/codecompletion.h b/libnymea-app/scripting/codecompletion.h index c200c49a..f8c02829 100644 --- a/libnymea-app/scripting/codecompletion.h +++ b/libnymea-app/scripting/codecompletion.h @@ -101,6 +101,7 @@ private: bool valid = false; QString name; QHash properties; + QStringList functions; int start = -1; int end = -1; }; diff --git a/libnymea-app/scripting/completionmodel.cpp b/libnymea-app/scripting/completionmodel.cpp index 45d6619a..2b418e78 100644 --- a/libnymea-app/scripting/completionmodel.cpp +++ b/libnymea-app/scripting/completionmodel.cpp @@ -81,6 +81,7 @@ CompletionModel::Entry CompletionModel::get(int index) return m_list.at(index); } + //************************************************ // CompletionProxyModel //************************************************ diff --git a/libnymea-app/scripting/completionmodel.h b/libnymea-app/scripting/completionmodel.h index 414ee3ea..0a4037ad 100644 --- a/libnymea-app/scripting/completionmodel.h +++ b/libnymea-app/scripting/completionmodel.h @@ -49,6 +49,9 @@ public: QString decoration; QString decorationProperty; QString trailingText; + bool operator==(const Entry &other) const { + return text == other.text && displayText == other.displayText; + } }; CompletionModel(QObject *parent = nullptr); diff --git a/libnymea-app/scriptsyntaxhighlighter.cpp b/libnymea-app/scriptsyntaxhighlighter.cpp index ce3f2e49..c355e8ee 100644 --- a/libnymea-app/scriptsyntaxhighlighter.cpp +++ b/libnymea-app/scriptsyntaxhighlighter.cpp @@ -38,6 +38,7 @@ #include #include #include +#include class ScriptSyntaxHighlighterPrivate: public QSyntaxHighlighter { @@ -62,7 +63,7 @@ private: }; struct HighlightingRule { - QRegExp pattern; + QRegularExpression pattern; QTextCharFormat format; }; QVector highlightingRules; @@ -118,19 +119,19 @@ void ScriptSyntaxHighlighterPrivate::update(bool dark) // ClassNames format.setForeground(dark ? QColor("#55fc49") : QColor("#800080")); - rule.pattern = QRegExp("\\b[A-Z][a-zA-Z0-9_]+\\b"); + rule.pattern = QRegularExpression("\\b[A-Z][a-zA-Z0-9_]+\\b"); rule.format = format; highlightingRules.append(rule); // Property bindings format.setForeground(dark ? QColor("#ff5555") : QColor("#800000")); - rule.pattern = QRegExp("[a-zA-Z][a-zA-Z0-9_.]+:"); + rule.pattern = QRegularExpression("[a-zA-Z][a-zA-Z0-9_.]+:"); rule.format = format; highlightingRules.append(rule); // imports format.clearForeground(); - rule.pattern = QRegExp("import .*$"); + rule.pattern = QRegularExpression("import .*$"); rule.format = format; highlightingRules.append(rule); @@ -158,13 +159,14 @@ void ScriptSyntaxHighlighterPrivate::update(bool dark) "\\bbool\\b", "\\bint\\b", "\\breal\\b", + "\\bdouble\\b", "\\bdate\\b", "\\btrue\\b", "\\bfalse\\b", }; format.setForeground(dark ? Qt::yellow : QColor("#80831a")); foreach (const QString &pattern, keywordPatterns) { - rule.pattern = QRegExp(pattern); + rule.pattern = QRegularExpression(pattern); rule.format = format; highlightingRules.append(rule); } @@ -172,17 +174,15 @@ void ScriptSyntaxHighlighterPrivate::update(bool dark) // String literals format.setForeground(dark ? QColor("#e64ad7") : Qt::darkGreen); rule.format = format; - rule.pattern = QRegExp("\".[^\"]*\""); - highlightingRules.append(rule); - rule.pattern = QRegExp("'.[^']*'"); + rule.pattern = QRegularExpression(R"**((?= 0) { - int length = expression.matchedLength(); - if (text.mid(index, length).endsWith(':')) { - length--; - } - setFormat(index, length, rule.format); - index = expression.indexIn(text, index + length); + QRegularExpression expression(rule.pattern); + QRegularExpressionMatchIterator matches = expression.globalMatch(text); + while (matches.hasNext()) { + QRegularExpressionMatch match = matches.next(); + setFormat(match.capturedStart(), match.capturedLength(), rule.format); } } if (text.trimmed().startsWith("import")) {