diff --git a/libnymea-app/scripting/codecompletion.cpp b/libnymea-app/scripting/codecompletion.cpp index 16d751ab..995d0145 100644 --- a/libnymea-app/scripting/codecompletion.cpp +++ b/libnymea-app/scripting/codecompletion.cpp @@ -31,6 +31,8 @@ #include "codecompletion.h" #include "engine.h" +#include "types/interfaces.h" +#include "types/interface.h" #include #include @@ -44,9 +46,8 @@ CodeCompletion::CodeCompletion(QObject *parent): m_classes.insert("ThingAction", ClassInfo("ThingAction", {"id", "thingId", "actionTypeId", "actionName"}, {"execute"})); m_classes.insert("ThingState", ClassInfo("ThingState", {"id", "thingId", "stateTypeId", "stateName", "value"}, {}, {"onValueChanged"})); m_classes.insert("ThingEvent", ClassInfo("ThingEvent", {"id", "thingId", "eventTypeId", "eventName"}, {}, {"onTriggered"})); - m_classes.insert("DeviceAction", ClassInfo("DeviceAction", {"id", "deviceId", "actionTypeId", "actionName"}, {"execute"})); - m_classes.insert("DeviceState", ClassInfo("DeviceState", {"id", "deviceId", "stateTypeId", "stateName", "value"}, {}, {"onValueChanged"})); - m_classes.insert("DeviceEvent", ClassInfo("DeviceEvent", {"id", "deviceId", "eventTypeId", "eventName"}, {}, {"onTriggered"})); + m_classes.insert("InterfaceAction", ClassInfo("InterfaceAction", {"id", "interfaceName", "actionName"}, {"execute"})); + m_classes.insert("InterfaceEvent", ClassInfo("InterfaceEvent", {"id", "interfaceName", "eventName"}, {}, {"onTriggered"})); m_classes.insert("Timer", ClassInfo("Timer", {"id", "interval", "running", "repeat"}, {"start", "stop"}, {"onTriggered"})); m_classes.insert("Alarm", ClassInfo("Alarm", {"id", "time", "endTime", "weekDays", "active"}, {}, {"onTriggered", "onActiveChanged"})); m_classes.insert("PropertyAnimation", ClassInfo("PropertyAnimation", {"id", "target", "targets", "property", "properties", "value", "from", "to", "easing", "exclude", "duration", "alwaysRunToEnd", "loops", "paused", "running"}, {"start", "stop", "pause", "resume", "complete"}, {"onStarted", "onStopped", "onFinished", "onRunningChanged"})); @@ -178,30 +179,14 @@ void CodeCompletion::update() return; } - QRegExp deviceIdExp(".*deviceId: \"[a-zA-ZÀ-ž0-9- ]*"); - if (deviceIdExp.exactMatch(blockText)) { - for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); i++) { - Device *dev = m_engine->deviceManager()->devices()->get(i); - entries.append(CompletionModel::Entry(dev->id().toString() + "\" // " + dev->name(), dev->name(), "thing", dev->thingClass()->interfaces().join(","))); - } - blockText.remove(QRegExp(".*deviceId: \"")); - m_model->update(entries); - m_proxy->setFilter(blockText, false); - emit hint(); - return; - } - QRegExp stateTypeIdExp(".*stateTypeId: \"[a-zA-Z0-9-]*"); if (stateTypeIdExp.exactMatch(blockText)) { BlockInfo info = getBlockInfo(m_cursor.position()); QString thingId; - if (info.properties.contains("thingId")) { - thingId = info.properties.value("thingId"); - } else if (info.properties.contains("deviceId")) { - thingId = info.properties.value("deviceId"); - } else { + if (!info.properties.contains("thingId")) { return; } + thingId = info.properties.value("thingId"); qDebug() << "selected thingId" << thingId; Device *device = m_engine->deviceManager()->devices()->getDevice(thingId); @@ -221,20 +206,17 @@ void CodeCompletion::update() } QRegExp stateNameExp(".*stateName: \"[a-zA-Z0-9-]*"); - qDebug() << "block text" << blockText << stateNameExp.exactMatch(blockText); +// qDebug() << "block text" << blockText << stateNameExp.exactMatch(blockText); if (stateNameExp.exactMatch(blockText)) { BlockInfo info = getBlockInfo(m_cursor.position()); qDebug() << "stateName block info" << info.name << info.properties; QString thingId; - if (info.properties.contains("thingId")) { - thingId = info.properties.value("thingId"); - } else if (info.properties.contains("deviceId")) { - thingId = info.properties.value("deviceId"); - } else { + if (!info.properties.contains("thingId")) { return; } + thingId = info.properties.value("thingId"); - qDebug() << "selected deviceId" << thingId; + qDebug() << "selected thingId" << thingId; Device *device = m_engine->deviceManager()->devices()->getDevice(thingId); if (!device) { return; @@ -256,15 +238,12 @@ void CodeCompletion::update() if (actionTypeIdExp.exactMatch(blockText)) { BlockInfo info = getBlockInfo(m_cursor.position()); QString thingId; - if (info.properties.contains("thingId")) { - thingId = info.properties.value("thingId"); - } else if (info.properties.contains("deviceId")) { - thingId = info.properties.value("deviceId"); - } else { + if (!info.properties.contains("thingId")) { return; } + thingId = info.properties.value("thingId"); - qDebug() << "selected deviceId" << thingId; + qDebug() << "selected thingId" << thingId; Device *device = m_engine->deviceManager()->devices()->getDevice(thingId); if (!device) { return; @@ -284,25 +263,34 @@ void CodeCompletion::update() QRegExp actionNameExp(".*actionName: \"[a-zA-Z0-9-]*"); if (actionNameExp.exactMatch(blockText)) { BlockInfo info = getBlockInfo(m_cursor.position()); - QString thingId; + Interfaces ifaces; + + ActionTypes *actionTypes = nullptr; + if (info.properties.contains("thingId")) { - thingId = info.properties.value("thingId"); - } else if (info.properties.contains("deviceId")) { - thingId = info.properties.value("deviceId"); + QString thingId = info.properties.value("thingId"); + qDebug() << "selected thingId" << thingId; + Device *thing = m_engine->thingManager()->things()->getThing(thingId); + if (!thing) { + return; + } + actionTypes = thing->thingClass()->actionTypes(); + } else if (info.properties.contains("interfaceName")) { + QString interfaceName = info.properties.value("interfaceName"); + Interface *iface = ifaces.findByName(interfaceName); + if (!iface) { + return; + } + actionTypes = iface->actionTypes(); } else { return; } - qDebug() << "selected deviceId" << thingId; - Device *device = m_engine->deviceManager()->devices()->getDevice(thingId); - if (!device) { - return; - } - - for (int i = 0; i < device->thingClass()->actionTypes()->rowCount(); i++) { - ActionType *actionType = device->thingClass()->actionTypes()->get(i); + for (int i = 0; i < actionTypes->rowCount(); i++) { + ActionType *actionType = actionTypes->get(i); entries.append(CompletionModel::Entry(actionType->name() + "\"", actionType->name(), "actionType")); } + blockText.remove(QRegExp(".*actionName: \"")); m_model->update(entries); m_proxy->setFilter(blockText); @@ -314,15 +302,12 @@ void CodeCompletion::update() if (eventTypeIdExp.exactMatch(blockText)) { BlockInfo info = getBlockInfo(m_cursor.position()); QString thingId; - if (info.properties.contains("thingId")) { - thingId = info.properties.value("thingId"); - } else if (info.properties.contains("deviceId")) { - thingId = info.properties.value("deviceId"); - } else { + if (!info.properties.contains("thingId")) { return; } + thingId = info.properties.value("thingId"); - qDebug() << "selected deviceId" << thingId; + qDebug() << "selected thingId" << thingId; Device *device = m_engine->deviceManager()->devices()->getDevice(thingId); if (!device) { return; @@ -342,24 +327,29 @@ void CodeCompletion::update() QRegExp eventNameExp(".*eventName: \"[a-zA-Z0-9-]*"); if (eventNameExp.exactMatch(blockText)) { BlockInfo info = getBlockInfo(m_cursor.position()); - QString thingId; + Interfaces ifaces; + EventTypes *eventTypes = nullptr; if (info.properties.contains("thingId")) { - thingId = info.properties.value("thingId"); - } else if (info.properties.contains("deviceId")) { - thingId = info.properties.value("deviceId"); + QString thingId = info.properties.value("thingId"); + Device *thing = m_engine->thingManager()->things()->getThing(thingId); + if (!thing) { + return; + } + eventTypes = thing->thingClass()->eventTypes(); + + } else if (info.properties.contains("interfaceName")) { + QString interfaceName = info.properties.value("interfaceName"); + Interface *iface = ifaces.findByName(interfaceName); + if (!iface) { + return; + } + eventTypes = iface->eventTypes(); } else { return; } - qDebug() << "selected deviceId" << thingId; - - Device *device = m_engine->deviceManager()->devices()->getDevice(thingId); - if (!device) { - return; - } - - for (int i = 0; i < device->thingClass()->eventTypes()->rowCount(); i++) { - EventType *eventType = device->thingClass()->eventTypes()->get(i); + for (int i = 0; i < eventTypes->rowCount(); i++) { + EventType *eventType = eventTypes->get(i); entries.append(CompletionModel::Entry(eventType->name() + "\"", eventType->name(), "eventType")); } blockText.remove(QRegExp(".*eventName: \"")); @@ -369,6 +359,22 @@ void CodeCompletion::update() return; } + QRegExp interfaceNameExp(".*interfaceName: \"[a-zA-Z]*"); + if (interfaceNameExp.exactMatch(blockText)) { + BlockInfo info = getBlockInfo(m_cursor.position()); + + Interfaces ifaces; + for (int i = 0; i < ifaces.rowCount(); i++) { + Interface *iface = ifaces.get(i); + entries.append(CompletionModel::Entry(iface->name() + "\"", iface->name(), "interface", iface->name())); + } + m_model->update(entries); + blockText.remove(QRegExp(".*interfaceName: \"")); + m_proxy->setFilter(blockText); + emit hint(); + return; + } + QRegExp importExp("imp(o|or)?"); if (importExp.exactMatch(blockText)) { entries.append(CompletionModel::Entry("import ", "import", "keyword", "")); @@ -436,9 +442,6 @@ void CodeCompletion::update() qDebug() << "actionType" << info.properties.keys() << info.properties.value("actionName") << info.properties.value("actionTypeId"); if (info.valid) { QString thingId = info.properties.value("thingId"); - if (thingId.isEmpty()) { - thingId = info.properties.value("deviceId"); - } Device *d = m_engine->thingManager()->things()->getDevice(QUuid(thingId)); if (d) { ActionType *at = nullptr; @@ -491,7 +494,7 @@ void CodeCompletion::update() bool isImperative = jsBlock.name.endsWith(":") || jsBlock.name.endsWith("()"); bool atStart = false; while (!isImperative && jsBlock.valid && !atStart) { - qDebug() << "is imperative block?" << isImperative << jsBlock.name << "blockText" << blockText; +// qDebug() << "is imperative block?" << isImperative << jsBlock.name << "blockText" << blockText; BlockInfo tmp = getBlockInfo(jsBlock.start - 1); if (tmp.valid) { jsBlock = tmp; @@ -501,7 +504,7 @@ void CodeCompletion::update() } } if (isImperative) { - qDebug() << "Is imperative!"; +// qDebug() << "Is imperative!"; // Starting a new expression? QRegExp newExpressionExp("(.*; [a-zA-Z0-9]*| *[a-zA-Z0-9]*)"); if (newExpressionExp.exactMatch(blockText)) { @@ -731,8 +734,8 @@ void CodeCompletion::complete(int index) QTextCursor tmp = m_cursor; tmp.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); QString blockText = tmp.selectedText(); - QRegExp deviceIdExp(".*deviceId: \"[a-zA-ZÀ-ž0-9- ]*"); - if (deviceIdExp.exactMatch(blockText)) { + QRegExp thingIdExp(".*thingId: \"[a-zA-ZÀ-ž0-9- ]*"); + if (thingIdExp.exactMatch(blockText)) { QTextCursor tmp = m_document->textDocument()->find("\"", m_cursor.position(), QTextDocument::FindBackward); m_cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, m_cursor.position() - tmp.position()); m_cursor.removeSelectedText(); @@ -846,6 +849,46 @@ void CodeCompletion::insertAfterCursor(const QString &text) emit cursorPositionChanged(); } +void CodeCompletion::toggleComment(int from, int to) +{ + bool textSelected = from != to; + QTextCursor tmp = QTextCursor(m_document->textDocument()); + tmp.setPosition(from); + tmp.movePosition(QTextCursor::StartOfLine); + + bool allLinesHaveComments = true; + do { + QTextCursor nextComment = m_document->textDocument()->find(QRegExp("^[ ]*//"), tmp.position()); + nextComment.movePosition(QTextCursor::StartOfLine); + bool lineHasComment = tmp.position() == nextComment.position(); + allLinesHaveComments &= lineHasComment; + tmp.movePosition(QTextCursor::EndOfLine); + tmp.movePosition(QTextCursor::NextCharacter); + } while (tmp.position() < to); + qDebug() << "All lines have comments:" << allLinesHaveComments; + + tmp.setPosition(from); + tmp.movePosition(QTextCursor::StartOfLine); + do { + if (allLinesHaveComments) { + QTextCursor nextComment = m_document->textDocument()->find(QRegExp("//"), tmp.position()); + nextComment.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2); + nextComment.removeSelectedText(); + nextComment.insertText(" "); + to -= 2; + } else { + tmp.insertText("//"); + to += 2; + } + tmp.movePosition(QTextCursor::EndOfLine); + tmp.movePosition(QTextCursor::NextCharacter); + } while (tmp.position() < to); + + if (textSelected) { + emit select(from, to); + } +} + void CodeCompletion::moveCursor(CodeCompletion::MoveOperation moveOperation, int count) { switch (moveOperation) { diff --git a/libnymea-app/scripting/codecompletion.h b/libnymea-app/scripting/codecompletion.h index 3694c425..c200c49a 100644 --- a/libnymea-app/scripting/codecompletion.h +++ b/libnymea-app/scripting/codecompletion.h @@ -83,6 +83,7 @@ public slots: void closeBlock(); void insertBeforeCursor(const QString &text); void insertAfterCursor(const QString &text); + void toggleComment(int from, int to); void moveCursor(MoveOperation moveOperation, int count = 1); @@ -92,6 +93,7 @@ signals: void cursorPositionChanged(); void currentWordChanged(); void hint(); + void select(int from, int to); private: class BlockInfo { diff --git a/nymea-app/ui/magic/ScriptEditor.qml b/nymea-app/ui/magic/ScriptEditor.qml index 90de300a..cf9b0129 100644 --- a/nymea-app/ui/magic/ScriptEditor.qml +++ b/nymea-app/ui/magic/ScriptEditor.qml @@ -231,6 +231,10 @@ Page { return event.modifiers & Qt.ControlModifier || event.modifiers & Qt.MetaModifier } + function shiftPressed(event) { + return event.modifiers & Qt.ShiftModifier + } + Keys.onPressed: { print("key", event.key, "Completion box visible:", completionBox.visible) // Things to happen only when we're not autocompleting @@ -301,7 +305,12 @@ Page { event.accepted = true; return; } - + case Qt.Key_Slash: + if (controlPressed(event)) { + completion.toggleComment(selectionStart, selectionEnd); + event.accepted = true; + return; + } } // Things to do only when we're autocompleting @@ -446,6 +455,7 @@ Page { cursorPosition: scriptEdit.cursorPosition onCursorPositionChanged: scriptEdit.cursorPosition = cursorPosition onHint: completionBox.show() + onSelect: scriptEdit.select(from, to) } BusyOverlay { diff --git a/nymea-app/ui/magic/scripting/CompletionBox.qml b/nymea-app/ui/magic/scripting/CompletionBox.qml index 92ef3972..cf34d8cf 100644 --- a/nymea-app/ui/magic/scripting/CompletionBox.qml +++ b/nymea-app/ui/magic/scripting/CompletionBox.qml @@ -172,6 +172,8 @@ Rectangle { return Qt.resolvedUrl("../../images/state.svg") case "actionType": return Qt.resolvedUrl("../../images/action.svg") + case "interface": + return app.interfaceToIcon(model.decorationProperty) } return "" }