initial work on a script editor
This commit is contained in:
parent
9d2828bfaf
commit
d1d599a8ec
@ -68,6 +68,7 @@
|
||||
#include "configuration/networkmanager.h"
|
||||
#include "types/networkdevices.h"
|
||||
#include "types/networkdevice.h"
|
||||
#include "scriptsyntaxhighlighter.h"
|
||||
|
||||
#include <QtQml/qqml.h>
|
||||
|
||||
@ -230,6 +231,9 @@ void registerQmlTypes() {
|
||||
qmlRegisterUncreatableType<NetworkDevice>(uri, 1, 0, "NetworkDevice", "Get it from NetworkDevices");
|
||||
qmlRegisterUncreatableType<WiredNetworkDevice>(uri, 1, 0, "WiredNetworkDevice", "Get it from NetworkDevices");
|
||||
qmlRegisterUncreatableType<WirelessNetworkDevice>(uri, 1, 0, "WirelessNetworkDevice", "Get it from NetworkDevices");
|
||||
|
||||
qmlRegisterType<ScriptSyntaxHighlighter>(uri, 1, 0, "ScriptSyntaxHighlighter");
|
||||
qmlRegisterUncreatableType<CompletionProxyModel>(uri, 1, 0, "CompletionProxyModel", "Get it from ScriptSyntaxHighlighter");
|
||||
}
|
||||
|
||||
#endif // LIBNYMEAAPPCORE_H
|
||||
|
||||
@ -15,7 +15,7 @@ include(../nymea-remoteproxy/libnymea-remoteproxyclient/libnymea-remoteproxyclie
|
||||
|
||||
|
||||
QT -= gui
|
||||
QT += network websockets bluetooth charts
|
||||
QT += network websockets bluetooth charts quick
|
||||
|
||||
LIBS += -lssl -lcrypto
|
||||
|
||||
@ -48,6 +48,7 @@ SOURCES += \
|
||||
devicediscovery.cpp \
|
||||
models/packagesfiltermodel.cpp \
|
||||
models/taglistmodel.cpp \
|
||||
scriptsyntaxhighlighter.cpp \
|
||||
vendorsproxy.cpp \
|
||||
pluginsproxy.cpp \
|
||||
interfacesmodel.cpp \
|
||||
@ -111,6 +112,7 @@ HEADERS += \
|
||||
devicediscovery.h \
|
||||
models/packagesfiltermodel.h \
|
||||
models/taglistmodel.h \
|
||||
scriptsyntaxhighlighter.h \
|
||||
vendorsproxy.h \
|
||||
pluginsproxy.h \
|
||||
interfacesmodel.h \
|
||||
|
||||
342
libnymea-app-core/scriptsyntaxhighlighter.cpp
Normal file
342
libnymea-app-core/scriptsyntaxhighlighter.cpp
Normal file
@ -0,0 +1,342 @@
|
||||
#include "scriptsyntaxhighlighter.h"
|
||||
|
||||
#include "engine.h"
|
||||
#include "devicemanager.h"
|
||||
#include "devices.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMetaObject>
|
||||
#include <QTextDocumentFragment>
|
||||
|
||||
class ScriptSyntaxHighlighterPrivate: public QSyntaxHighlighter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ScriptSyntaxHighlighterPrivate(QObject *parent);
|
||||
|
||||
protected:
|
||||
void highlightBlock(const QString &text) override;
|
||||
|
||||
signals:
|
||||
void contentChanged(const QString &text);
|
||||
|
||||
private:
|
||||
enum BlockState {
|
||||
BlockStateInvalid = -1,
|
||||
BlockStateNone = 0,
|
||||
BlockStateImport = 1,
|
||||
BlockStateAction,
|
||||
BlockstateDeviceId,
|
||||
};
|
||||
struct HighlightingRule
|
||||
{
|
||||
QRegExp pattern;
|
||||
QTextCharFormat format;
|
||||
};
|
||||
QVector<HighlightingRule> highlightingRules;
|
||||
|
||||
QTextCharFormat keywordFormat;
|
||||
QTextCharFormat propertyFormat;
|
||||
QTextCharFormat lookupFormat;
|
||||
QTextCharFormat quotationFormat;
|
||||
QTextCharFormat itemFormat;
|
||||
QTextCharFormat cppObjectFormat;
|
||||
};
|
||||
|
||||
ScriptSyntaxHighlighter::ScriptSyntaxHighlighter(QObject *parent) : QObject(parent)
|
||||
{
|
||||
m_completionModel = new CompletionModel(this);
|
||||
m_proxyModel = new CompletionProxyModel(m_completionModel, this);
|
||||
m_highlighter = new ScriptSyntaxHighlighterPrivate(this);
|
||||
|
||||
m_classes.insert("Action", {"id", "deviceId", "actionTypeId", "actionName"});
|
||||
}
|
||||
|
||||
Engine *ScriptSyntaxHighlighter::engine() const
|
||||
{
|
||||
return m_engine;
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighter::setEngine(Engine *engine)
|
||||
{
|
||||
if (m_engine != engine) {
|
||||
m_engine = engine;
|
||||
emit engineChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QQuickTextDocument *ScriptSyntaxHighlighter::document() const
|
||||
{
|
||||
return m_document;
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighter::setDocument(QQuickTextDocument *document)
|
||||
{
|
||||
if (m_document != document) {
|
||||
m_document = document;
|
||||
m_highlighter->setDocument(m_document->textDocument());
|
||||
|
||||
connect(document->textDocument(), &QTextDocument::cursorPositionChanged, this, &ScriptSyntaxHighlighter::onCursorPositionChanged);
|
||||
emit documentChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int ScriptSyntaxHighlighter::cursorPosition() const
|
||||
{
|
||||
return m_currentCursor.position();
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighter::setCursorPosition(int cursorPosition)
|
||||
{
|
||||
if (m_currentCursor.position() != cursorPosition) {
|
||||
m_currentCursor.setPosition(cursorPosition);
|
||||
// emit cursorPositionChanged();
|
||||
onCursorPositionChanged(m_currentCursor);
|
||||
}
|
||||
}
|
||||
|
||||
CompletionProxyModel *ScriptSyntaxHighlighter::completionModel() const
|
||||
{
|
||||
return m_proxyModel;
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighter::complete(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_proxyModel->rowCount()) {
|
||||
qWarning() << "Invalid index for completion";
|
||||
return;
|
||||
}
|
||||
CompletionModel::Entry entry = m_proxyModel->get(index);
|
||||
QString textToInsert = entry.text;
|
||||
|
||||
if (entry.addTrailingQuote) {
|
||||
textToInsert.append("\"");
|
||||
}
|
||||
if (entry.addComment) {
|
||||
textToInsert.append(" // " + entry.displayText);
|
||||
}
|
||||
// textToInsert.append("\n");
|
||||
m_currentCursor.select(QTextCursor::WordUnderCursor);
|
||||
m_currentCursor.removeSelectedText();
|
||||
m_currentCursor.insertText(textToInsert);
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighter::newLine()
|
||||
{
|
||||
QString line = m_currentCursor.block().text();
|
||||
QString trimmedLine = line;
|
||||
trimmedLine.remove(QRegExp("^[ ]+"));
|
||||
int indent = line.length() - trimmedLine.length();
|
||||
|
||||
m_currentCursor.insertText(QString("\n").leftJustified(indent + 1, ' '));
|
||||
if (m_currentCursor.block().previous().text().endsWith("{")) {
|
||||
m_document->textDocument()->indentWidth();
|
||||
m_currentCursor.insertText(" ");
|
||||
m_currentCursor.insertText(QString("\n").leftJustified(indent + 1, ' '));
|
||||
m_currentCursor.insertText("}");
|
||||
m_currentCursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor, 1);
|
||||
m_currentCursor.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor, 1);
|
||||
emit cursorPositionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighter::indent(int from, int to)
|
||||
{
|
||||
QTextCursor tmp = QTextCursor(m_document->textDocument());
|
||||
tmp.setPosition(from);
|
||||
if (from == to) {
|
||||
tmp.insertText(" ");
|
||||
} else {
|
||||
while (tmp.position() < to) {
|
||||
tmp.insertText(" ");
|
||||
to += 4;
|
||||
if (!tmp.movePosition(QTextCursor::NextBlock)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighter::unindent(int from, int to)
|
||||
{
|
||||
QTextCursor tmp = QTextCursor(m_document->textDocument());
|
||||
tmp.setPosition(from);
|
||||
tmp.movePosition(QTextCursor::StartOfLine);
|
||||
if (from == to) {
|
||||
if (tmp.block().text().startsWith(" ")) {
|
||||
tmp.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
|
||||
tmp.removeSelectedText();
|
||||
}
|
||||
} else {
|
||||
// Make sure all selected lines start with 4 empty spaces before we start editing
|
||||
bool ok = true;
|
||||
while (tmp.position() < to) {
|
||||
if (!tmp.block().text().startsWith(" ")) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (!tmp.movePosition(QTextCursor::NextBlock)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
tmp.setPosition(from);
|
||||
tmp.movePosition(QTextCursor::StartOfLine);
|
||||
while (tmp.position() < to) {
|
||||
tmp.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
|
||||
tmp.removeSelectedText();
|
||||
to -= 4;
|
||||
if (!tmp.movePosition(QTextCursor::NextBlock)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighter::closeBlock()
|
||||
{
|
||||
m_currentCursor.insertText("}");
|
||||
if (m_currentCursor.block().text().trimmed() == "}") {
|
||||
unindent(m_currentCursor.position(), m_currentCursor.position());
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighter::onCursorPositionChanged(const QTextCursor &cursor)
|
||||
{
|
||||
m_currentCursor = cursor;
|
||||
QTextCursor word = cursor;
|
||||
word.select(QTextCursor::WordUnderCursor);
|
||||
|
||||
QString blockText = cursor.block().text();
|
||||
m_completionModel->clear();
|
||||
m_proxyModel->setFilter(QString());
|
||||
if (!m_engine) {
|
||||
return;
|
||||
}
|
||||
QRegExp deviceIdExp(".*deviceId: \"[a-zA-Z0-9-]*");
|
||||
if (deviceIdExp.exactMatch(blockText)) {
|
||||
for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); i++) {
|
||||
Device *dev = m_engine->deviceManager()->devices()->get(i);
|
||||
m_completionModel->append(CompletionModel::Entry(dev->id().toString(), dev->name(), true, true));
|
||||
|
||||
}
|
||||
blockText.remove(QRegExp(".*deviceId: \""));
|
||||
m_proxyModel->setFilter(blockText);
|
||||
return;
|
||||
}
|
||||
|
||||
QRegExp importExp("imp(o|or)?");
|
||||
if (importExp.exactMatch(blockText)) {
|
||||
m_completionModel->append(CompletionModel::Entry("import ", "import"));
|
||||
m_proxyModel->setFilter(blockText);
|
||||
return;
|
||||
}
|
||||
|
||||
QRegExp importExp2("import [a-zA-Z]*");
|
||||
if (importExp2.exactMatch(blockText)) {
|
||||
m_completionModel->append(CompletionModel::Entry("QtQuick 2.0"));
|
||||
m_completionModel->append(CompletionModel::Entry("nymea 1.0"));
|
||||
blockText.remove("import ");
|
||||
m_proxyModel->setFilter(blockText);
|
||||
return;
|
||||
}
|
||||
|
||||
QRegExp expressionStartExp(" *[a-zA-Z0-9]*");
|
||||
if (expressionStartExp.exactMatch(blockText)) {
|
||||
QTextCursor blockStartCursor = m_document->textDocument()->find("{", m_currentCursor, QTextDocument::FindBackward);
|
||||
QTextCursor blockEndCursor = m_document->textDocument()->find("}", m_currentCursor, QTextDocument::FindBackward);
|
||||
while (!blockEndCursor.isNull() && blockEndCursor.position() > blockStartCursor.position()) {
|
||||
blockStartCursor = m_document->textDocument()->find("{", blockStartCursor, QTextDocument::FindBackward);
|
||||
blockEndCursor = m_document->textDocument()->find("}", blockEndCursor, QTextDocument::FindBackward);
|
||||
}
|
||||
QString className = blockStartCursor.block().text();
|
||||
className.remove(QRegExp(" *\\{"));
|
||||
while (className.contains(" ")) {
|
||||
className.remove(QRegExp(".* "));
|
||||
}
|
||||
qDebug() << "ClassName" << className << m_classes.value(className);
|
||||
foreach (const QString &s, m_classes.value(className)) {
|
||||
m_completionModel->append(CompletionModel::Entry(s + ": ", s));
|
||||
}
|
||||
blockText.remove(QRegExp(".* "));
|
||||
m_proxyModel->setFilter(blockText);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScriptSyntaxHighlighterPrivate::ScriptSyntaxHighlighterPrivate(QObject *parent):
|
||||
QSyntaxHighlighter(parent)
|
||||
{
|
||||
HighlightingRule rule;
|
||||
|
||||
keywordFormat.setForeground(Qt::blue);
|
||||
|
||||
QStringList keywordPatterns;
|
||||
keywordPatterns << "\\bif\\b" << "\\belse\\b" << "\\breturn\\b"<< "\\bimport\\b" << "\\bsignal\\b" << "\\bproperty\\b";
|
||||
foreach (const QString &pattern, keywordPatterns) {
|
||||
rule.pattern = QRegExp(pattern);
|
||||
rule.format = keywordFormat;
|
||||
highlightingRules.append(rule);
|
||||
}
|
||||
|
||||
propertyFormat.setForeground(Qt::darkRed);
|
||||
rule.pattern = QRegExp("[A-z]+:");
|
||||
rule.format = propertyFormat;
|
||||
highlightingRules.append(rule);
|
||||
|
||||
lookupFormat.setForeground(Qt::magenta);
|
||||
//lookupFormat.setBackground(Qt::black);
|
||||
rule.pattern = QRegExp("\\b[0-9]+\\b");
|
||||
rule.format = lookupFormat;
|
||||
highlightingRules.append(rule);
|
||||
|
||||
quotationFormat.setForeground(Qt::darkGreen);
|
||||
rule.pattern = QRegExp("\".*\"");
|
||||
rule.format = quotationFormat;
|
||||
highlightingRules.append(rule);
|
||||
rule.pattern = QRegExp("'.*'");
|
||||
rule.format = quotationFormat;
|
||||
highlightingRules.append(rule);
|
||||
|
||||
itemFormat.setForeground(QColor(Qt::red));
|
||||
//itemFormat.setFontWeight(QFont::Bold);
|
||||
rule.pattern = QRegExp("[A-Z][a-z]+ ");
|
||||
rule.format = itemFormat;
|
||||
highlightingRules.append(rule);
|
||||
|
||||
cppObjectFormat.setForeground(QColor(Qt::blue).lighter());
|
||||
cppObjectFormat.setFontItalic(true);
|
||||
rule.pattern = QRegExp("_[A-z]+");
|
||||
rule.format = cppObjectFormat;
|
||||
highlightingRules.append(rule);
|
||||
}
|
||||
|
||||
void ScriptSyntaxHighlighterPrivate::highlightBlock(const QString &text)
|
||||
{
|
||||
// qDebug() << "hightlightBlock called for" << text << previousBlockState() << currentBlock().text();
|
||||
|
||||
foreach(const HighlightingRule &rule, highlightingRules){
|
||||
QRegExp expression(rule.pattern);
|
||||
int index = expression.indexIn(text);
|
||||
while (index >= 0) {
|
||||
int length = expression.matchedLength();
|
||||
setFormat(index, length, rule.format);
|
||||
index = expression.indexIn(text, index + length);
|
||||
}
|
||||
}
|
||||
if (text.trimmed().startsWith("import")) {
|
||||
setCurrentBlockState(BlockStateImport);
|
||||
} else if (text.trimmed().startsWith("Action")) {
|
||||
setCurrentBlockState(BlockStateAction);
|
||||
} else if (text.trimmed().startsWith("deviceId:")) {
|
||||
setCurrentBlockState(BlockstateDeviceId);
|
||||
} else {
|
||||
setCurrentBlockState(0);
|
||||
}
|
||||
|
||||
emit contentChanged(text);
|
||||
}
|
||||
|
||||
#include "scriptsyntaxhighlighter.moc"
|
||||
166
libnymea-app-core/scriptsyntaxhighlighter.h
Normal file
166
libnymea-app-core/scriptsyntaxhighlighter.h
Normal file
@ -0,0 +1,166 @@
|
||||
#ifndef SCRIPTSYNTAXHIGHLIGHTER_H
|
||||
#define SCRIPTSYNTAXHIGHLIGHTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QAbstractItemDelegate>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class ScriptSyntaxHighlighterPrivate;
|
||||
class CompletionModel;
|
||||
class CompletionProxyModel;
|
||||
class Engine;
|
||||
|
||||
class ScriptSyntaxHighlighter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged)
|
||||
Q_PROPERTY(QQuickTextDocument* document READ document WRITE setDocument NOTIFY documentChanged)
|
||||
Q_PROPERTY(CompletionProxyModel* completionModel READ completionModel CONSTANT)
|
||||
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
|
||||
public:
|
||||
explicit ScriptSyntaxHighlighter(QObject *parent = nullptr);
|
||||
|
||||
Engine* engine() const;
|
||||
void setEngine(Engine* engine);
|
||||
|
||||
QQuickTextDocument* document() const;
|
||||
void setDocument(QQuickTextDocument *document);
|
||||
|
||||
int cursorPosition() const;
|
||||
void setCursorPosition(int cursorPosition);
|
||||
|
||||
CompletionProxyModel* completionModel() const;
|
||||
|
||||
public slots:
|
||||
void complete(int index);
|
||||
void newLine();
|
||||
void indent(int from, int to);
|
||||
void unindent(int from, int to);
|
||||
void closeBlock();
|
||||
|
||||
signals:
|
||||
void documentChanged();
|
||||
void engineChanged();
|
||||
void cursorPositionChanged();
|
||||
|
||||
private slots:
|
||||
void onCursorPositionChanged(const QTextCursor &cursor);
|
||||
|
||||
private:
|
||||
ScriptSyntaxHighlighterPrivate *m_highlighter = nullptr;
|
||||
QQuickTextDocument* m_document = nullptr;
|
||||
CompletionModel* m_completionModel = nullptr;
|
||||
CompletionProxyModel* m_proxyModel = nullptr;
|
||||
Engine *m_engine = nullptr;
|
||||
QTextCursor m_currentCursor;
|
||||
|
||||
QHash<QString, QStringList> m_classes;
|
||||
};
|
||||
|
||||
|
||||
class CompletionModel: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
public:
|
||||
class Entry {
|
||||
public:
|
||||
Entry(const QString &text, const QString &displayText, bool addTrailingQuote = false, bool addComment = false)
|
||||
: text(text), displayText(displayText), addTrailingQuote(addTrailingQuote), addComment(addComment) {}
|
||||
Entry(const QString &text): text(text), displayText(text) {}
|
||||
QString text;
|
||||
QString displayText;
|
||||
bool addTrailingQuote = false;
|
||||
bool addComment = false;
|
||||
};
|
||||
CompletionModel(QObject *parent = nullptr): QAbstractListModel(parent) {}
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
|
||||
Q_UNUSED(parent)
|
||||
return m_list.count();
|
||||
}
|
||||
QHash<int, QByteArray> roleNames() const override {
|
||||
QHash<int, QByteArray> roles;
|
||||
roles.insert(Qt::UserRole, "text");
|
||||
roles.insert(Qt::DisplayRole, "displayText");
|
||||
return roles;
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
|
||||
Q_UNUSED(role)
|
||||
switch (role) {
|
||||
case Qt::UserRole:
|
||||
return m_list.at(index.row()).text;
|
||||
case Qt::DisplayRole:
|
||||
return m_list.at(index.row()).displayText;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
void clear() {
|
||||
beginResetModel();
|
||||
m_list.clear();
|
||||
endResetModel();
|
||||
emit countChanged();
|
||||
}
|
||||
void append(const Entry &entry) {
|
||||
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
|
||||
m_list.append(entry);
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
}
|
||||
Entry get(int index) {
|
||||
return m_list.at(index);
|
||||
}
|
||||
signals:
|
||||
void countChanged();
|
||||
private:
|
||||
QList<Entry> m_list;
|
||||
};
|
||||
|
||||
class CompletionProxyModel: public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged)
|
||||
public:
|
||||
CompletionProxyModel(CompletionModel *model, QObject *parent = nullptr): QSortFilterProxyModel(parent), m_model(model) {
|
||||
setSourceModel(model);
|
||||
connect(model, &CompletionModel::countChanged, this, &CompletionProxyModel::countChanged);
|
||||
sort(0);
|
||||
}
|
||||
|
||||
CompletionModel::Entry get(int index) {
|
||||
return m_model->get(mapToSource(this->index(index, 0)).row());
|
||||
}
|
||||
|
||||
QString filter() const {
|
||||
return m_filter;
|
||||
}
|
||||
void setFilter(const QString &filter) {
|
||||
if (m_filter != filter) {
|
||||
m_filter = filter;
|
||||
emit filterChanged();
|
||||
invalidateFilter();
|
||||
emit countChanged();
|
||||
}
|
||||
}
|
||||
protected:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &/*source_parent*/) const override {
|
||||
if (!m_filter.isEmpty()) {
|
||||
CompletionModel::Entry entry = m_model->get(source_row);
|
||||
if (!entry.displayText.startsWith(m_filter) && !entry.text.startsWith(m_filter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
signals:
|
||||
void filterChanged();
|
||||
void countChanged();
|
||||
private:
|
||||
CompletionModel *m_model = nullptr;
|
||||
QString m_filter;
|
||||
};
|
||||
|
||||
#endif // SCRIPTSYNTAXHIGHLIGHTER_H
|
||||
@ -203,5 +203,6 @@
|
||||
<file>ui/delegates/ThingTile.qml</file>
|
||||
<file>ui/components/TimePicker.qml</file>
|
||||
<file>ui/components/DatePicker.qml</file>
|
||||
<file>ui/magic/ScriptEditor.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -10,6 +10,13 @@ Page {
|
||||
text: qsTr("Magic")
|
||||
onBackPressed: pageStack.pop()
|
||||
|
||||
HeaderButton {
|
||||
imageSource: Qt.resolvedUrl("images/magic.svg")
|
||||
onClicked: {
|
||||
pageStack.push("magic/ScriptEditor.qml")
|
||||
}
|
||||
}
|
||||
|
||||
HeaderButton {
|
||||
imageSource: Qt.resolvedUrl("images/add.svg")
|
||||
onClicked: {
|
||||
|
||||
137
nymea-app/ui/magic/ScriptEditor.qml
Normal file
137
nymea-app/ui/magic/ScriptEditor.qml
Normal file
@ -0,0 +1,137 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.2
|
||||
import "../components"
|
||||
import Nymea 1.0
|
||||
|
||||
Page {
|
||||
id: root
|
||||
|
||||
header: NymeaHeader {
|
||||
text: qsTr("Script editor")
|
||||
onBackPressed: pageStack.pop()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "white"
|
||||
anchors.fill: parent
|
||||
|
||||
TextEdit {
|
||||
id: scriptEdit
|
||||
anchors.fill: parent
|
||||
font.family: "Monospace"
|
||||
Keys.onPressed: {
|
||||
print("key", event.key)
|
||||
// Things only to happen when we're not autocompleting
|
||||
if (!completionBox.visible) {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
syntax.newLine();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Tab:
|
||||
syntax.indent(selectionStart, selectionEnd);
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Backtab:
|
||||
syntax.unindent(selectionStart, selectionEnd);
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// things to happen in any case
|
||||
switch (event.key) {
|
||||
case Qt.Key_BraceRight:
|
||||
syntax.closeBlock();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Things to do only when we're autocompleting
|
||||
if (completionBox.visible) {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Escape:
|
||||
completionBox.hide();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Down:
|
||||
completionBox.next();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Up:
|
||||
completionBox.previous();
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Enter:
|
||||
case Qt.Key_Return:
|
||||
syntax.complete(completionBox.currentIndex)
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: completionBox
|
||||
border.width: 1
|
||||
border.color: "black"
|
||||
height: syntax.completionModel.count * 30
|
||||
width: 200
|
||||
x: scriptEdit.cursorRectangle.x
|
||||
y: scriptEdit.cursorRectangle.y + scriptEdit.cursorRectangle.height
|
||||
visible: syntax.completionModel.count > 0 && !hidden
|
||||
property bool hidden: false
|
||||
Connections {
|
||||
target: syntax.completionModel
|
||||
onCountChanged: {
|
||||
completionBox.hidden = false;
|
||||
completionBox.currentIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
property int currentIndex: 0
|
||||
function next() { currentIndex = (currentIndex + 1) % syntax.completionModel.count}
|
||||
function previous() {
|
||||
currentIndex--;
|
||||
if (currentIndex < 0) {
|
||||
currentIndex = syntax.completionModel.count - 1
|
||||
}
|
||||
}
|
||||
function hide() {
|
||||
hidden = true;
|
||||
}
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
model: syntax.completionModel
|
||||
delegate: Rectangle {
|
||||
height: 30
|
||||
width: parent.width
|
||||
color: index == completionBox.currentIndex ? "blue" : "white"
|
||||
Label {
|
||||
text: model.displayText
|
||||
color: "black"
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
syntax.complete(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScriptSyntaxHighlighter {
|
||||
id: syntax
|
||||
engine: _engine
|
||||
document: scriptEdit.textDocument
|
||||
cursorPosition: scriptEdit.cursorPosition
|
||||
onCursorPositionChanged: scriptEdit.cursorPosition = cursorPosition
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user