From 0f7f58417bc2ffbaa1a31e918a1b5b928edb0fbf Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 19 Jan 2018 19:11:12 +0100 Subject: [PATCH 01/15] JS scripting console auto-complete --- interface/CMakeLists.txt | 10 + interface/src/ui/JSConsole.cpp | 279 ++++++++++++++---- interface/src/ui/JSConsole.h | 16 +- .../src/UsersScriptingInterface.h | 2 +- tools/jsdoc/config.json | 5 +- tools/jsdoc/package.json | 7 + tools/jsdoc/plugins/hifi.js | 8 + tools/jsdoc/plugins/hifiJSONExport.js | 14 + 8 files changed, 285 insertions(+), 56 deletions(-) create mode 100644 tools/jsdoc/package.json create mode 100644 tools/jsdoc/plugins/hifiJSONExport.js diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 21225756b4..fbc40f70c2 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -349,6 +349,16 @@ endif() add_bugsplat() +# generate the JSDoc JSON for the JSConsole auto-completer +add_custom_command(TARGET ${TARGET_NAME} #POST_BUILD + COMMAND npm install + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/jsdoc +) +add_custom_command(TARGET ${TARGET_NAME} + COMMAND node_modules/.bin/jsdoc . -c config.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/jsdoc +) + if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index c5f8b54ebd..7f68a205e6 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -12,10 +12,11 @@ #include "JSConsole.h" #include -#include #include #include #include +#include +#include #include #include @@ -61,12 +62,64 @@ void _writeLines(const QString& filename, const QList& lines) { QTextStream(&file) << json; } +void JSConsole::readAPI() { + QFile file(PathUtils::resourcesPath() + "auto-complete/export.json"); + file.open(QFile::ReadOnly); + auto json = QTextStream(&file).readAll().toUtf8(); + _apiDocs = QJsonDocument::fromJson(json).array(); +} + +QStandardItem* getAutoCompleteItem(QJsonValue propertyObject) { + auto propertyItem = new QStandardItem(propertyObject.toObject().value("name").toString()); + propertyItem->setData(propertyObject.toVariant()); + return propertyItem; +} + +QStandardItemModel* JSConsole::getAutoCompleteModel(const QString& memberOf) { + QString memberOfProperty = nullptr; + + auto model = new QStandardItemModel(this); + + if (memberOf != nullptr) { + foreach(auto doc, _apiDocs) { + auto object = doc.toObject(); + if (object.value("name").toString() == memberOf && object.value("scope").toString() == "global" && + object.value("kind").toString() == "namespace") { + + memberOfProperty = object.value("longname").toString(); + + auto properties = doc.toObject().value("properties").toArray(); + foreach(auto propertyObject, properties) { + model->appendRow(getAutoCompleteItem(propertyObject)); + } + } + } + if (memberOfProperty == nullptr) { + return nullptr; + } + } + + foreach(auto doc, _apiDocs) { + auto object = doc.toObject(); + auto scope = object.value("scope"); + if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") || (memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty)) { + model->appendRow(getAutoCompleteItem(doc)); + } + } + model->sort(0); + return model; +} + JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) : QWidget(parent), _ui(new Ui::Console), _currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND), _savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + HISTORY_FILENAME), - _commandHistory(_readLines(_savedHistoryFilename)) { + _commandHistory(_readLines(_savedHistoryFilename)), + _completer(new QCompleter(this)) { + + readAPI(); + _ui->setupUi(this); _ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap); _ui->promptTextEdit->setWordWrapMode(QTextOption::NoWrap); @@ -78,38 +131,75 @@ JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) : setStyleSheet(styleSheet.readAll()); } - connect(_ui->scrollArea->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(scrollToBottom())); - connect(_ui->promptTextEdit, SIGNAL(textChanged()), this, SLOT(resizeTextInput())); + connect(_ui->scrollArea->verticalScrollBar(), &QScrollBar::rangeChanged, this, &JSConsole::scrollToBottom); + connect(_ui->promptTextEdit, &QTextEdit::textChanged, this, &JSConsole::resizeTextInput); + + _completer->setWidget(_ui->promptTextEdit); + _completer->setModel(getAutoCompleteModel(nullptr)); + _completer->setModelSorting(QCompleter::CaseSensitivelySortedModel); + _completer->setMaxVisibleItems(12); + _completer->setFilterMode(Qt::MatchStartsWith); + _completer->setWrapAround(false); + _completer->setCompletionMode(QCompleter::PopupCompletion); + _completer->setCaseSensitivity(Qt::CaseSensitive); + + QListView *listView = new QListView(); + listView->setEditTriggers(QAbstractItemView::NoEditTriggers); + listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + listView->setSelectionBehavior(QAbstractItemView::SelectRows); + listView->setSelectionMode(QAbstractItemView::SingleSelection); + listView->setModelColumn(_completer->completionColumn()); + + _completer->setPopup(listView); + _completer->popup()->installEventFilter(this); + QObject::connect(_completer, static_cast(&QCompleter::activated), this, + &JSConsole::insertCompletion); + + QObject::connect(_completer, static_cast(&QCompleter::highlighted), this, + &JSConsole::highlightedCompletion); setScriptEngine(scriptEngine); resizeTextInput(); - connect(&_executeWatcher, SIGNAL(finished()), this, SLOT(commandFinished())); + connect(&_executeWatcher, &QFutureWatcher::finished, this, &JSConsole::commandFinished); +} + +void JSConsole::insertCompletion(const QModelIndex& completion) { + auto completionString = completion.data().toString(); + QTextCursor tc = _ui->promptTextEdit->textCursor(); + int extra = completionString.length() - _completer->completionPrefix().length(); + tc.movePosition(QTextCursor::Left); + tc.movePosition(QTextCursor::EndOfWord); + tc.insertText(completionString.right(extra)); + _ui->promptTextEdit->setTextCursor(tc); +} + +void JSConsole::highlightedCompletion(const QModelIndex& completion) { + qDebug() << "Highlighted " << completion.data().toString(); + auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject(); + + // qDebug() << "Highlighted data " << QJsonDocument(jsdocObject).toJson(QJsonDocument::Compact); } JSConsole::~JSConsole() { if (_scriptEngine) { - disconnect(_scriptEngine.data(), SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&))); - disconnect(_scriptEngine.data(), SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&))); + disconnect(_scriptEngine.data(), nullptr, this, nullptr); _scriptEngine.reset(); } delete _ui; } void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) { - if (_scriptEngine == scriptEngine && scriptEngine != NULL) { + if (_scriptEngine == scriptEngine && scriptEngine != nullptr) { return; } - if (_scriptEngine != NULL) { - disconnect(_scriptEngine.data(), &ScriptEngine::printedMessage, this, &JSConsole::handlePrint); - disconnect(_scriptEngine.data(), &ScriptEngine::infoMessage, this, &JSConsole::handleInfo); - disconnect(_scriptEngine.data(), &ScriptEngine::warningMessage, this, &JSConsole::handleWarning); - disconnect(_scriptEngine.data(), &ScriptEngine::errorMessage, this, &JSConsole::handleError); + if (_scriptEngine != nullptr) { + disconnect(_scriptEngine.data(), nullptr, this, nullptr); _scriptEngine.reset(); } - // if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine + // if scriptEngine is nullptr then create one and keep track of it using _ownScriptEngine if (scriptEngine.isNull()) { _scriptEngine = DependencyManager::get()->loadScript(_consoleFileName, false); } else { @@ -199,45 +289,132 @@ void JSConsole::showEvent(QShowEvent* event) { } bool JSConsole::eventFilter(QObject* sender, QEvent* event) { - if (sender == _ui->promptTextEdit) { - if (event->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast(event); - int key = keyEvent->key(); + if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + int key = keyEvent->key(); - if ((key == Qt::Key_Return || key == Qt::Key_Enter)) { - if (keyEvent->modifiers() & Qt::ShiftModifier) { - // If the shift key is being used then treat it as a regular return/enter. If this isn't done, - // a new QTextBlock isn't created. - keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier); - } else { - QString command = _ui->promptTextEdit->toPlainText().replace("\r\n","\n").trimmed(); - - if (!command.isEmpty()) { - QTextCursor cursor = _ui->promptTextEdit->textCursor(); - _ui->promptTextEdit->clear(); - - executeCommand(command); - } - - return true; - } - } else if (key == Qt::Key_Down) { - // Go to the next command in history if the cursor is at the last line of the current command. - int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); - int blockCount = _ui->promptTextEdit->document()->blockCount(); - if (blockNumber == blockCount - 1) { - setToNextCommandInHistory(); - return true; - } - } else if (key == Qt::Key_Up) { - // Go to the previous command in history if the cursor is at the first line of the current command. - int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); - if (blockNumber == 0) { - setToPreviousCommandInHistory(); - return true; - } + if (_completer->popup()->isVisible()) { + // The following keys are forwarded by the completer to the widget + switch (key) { + case Qt::Key_Space: + case Qt::Key_Enter: + case Qt::Key_Return: + insertCompletion(_completer->currentIndex()); + _completer->popup()->hide(); + return true; + case Qt::Key_Escape: + case Qt::Key_Tab: + case Qt::Key_Backtab: + qDebug() << "test"; + keyEvent->ignore();//setAccepted(false); + return false; // let the completer do default behavior + default: + return false; } } + + if ((key == Qt::Key_Return || key == Qt::Key_Enter)) { + if (keyEvent->modifiers() & Qt::ShiftModifier) { + // If the shift key is being used then treat it as a regular return/enter. If this isn't done, + // a new QTextBlock isn't created. + keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier); + } else { + QString command = _ui->promptTextEdit->toPlainText().replace("\r\n", "\n").trimmed(); + + if (!command.isEmpty()) { + QTextCursor cursor = _ui->promptTextEdit->textCursor(); + _ui->promptTextEdit->clear(); + + executeCommand(command); + } + + return true; + } + } else if (key == Qt::Key_Down) { + // Go to the next command in history if the cursor is at the last line of the current command. + int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); + int blockCount = _ui->promptTextEdit->document()->blockCount(); + if (blockNumber == blockCount - 1) { + setToNextCommandInHistory(); + return true; + } + } else if (key == Qt::Key_Up) { + // Go to the previous command in history if the cursor is at the first line of the current command. + int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); + if (blockNumber == 0) { + setToPreviousCommandInHistory(); + return true; + } + } + } else if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyRelease) { + QKeyEvent* keyEvent = static_cast(event); + int key = keyEvent->key(); + + // completer shortcut (CTRL + SPACE) + bool isCompleterShortcut = ((keyEvent->modifiers() & Qt::ControlModifier) && key == Qt::Key_Space) || + key == Qt::Key_Period; + if (_completer->popup()->isVisible() || isCompleterShortcut) { + + const bool ctrlOrShift = keyEvent->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier); + if (ctrlOrShift && keyEvent->text().isEmpty()) { + return false; + } + + static QString eow("~!@#$%^&*()+{}|:\"<>?,/;'[]\\-="); // end of word + bool hasModifier = (keyEvent->modifiers() != Qt::NoModifier) && !ctrlOrShift; + + + if (!isCompleterShortcut && (!keyEvent->text().isEmpty() && eow.contains(keyEvent->text().right(1)))) { + qDebug() << "eow contains " << keyEvent->text().right(1) << " full text: " << keyEvent->text(); + _completer->popup()->hide(); + return false; + } + qDebug() << "auto completing"; + + auto textCursor = _ui->promptTextEdit->textCursor(); + + textCursor.select(QTextCursor::WordUnderCursor); + + QString completionPrefix = textCursor.selectedText(); + + auto leftOfCursor = _ui->promptTextEdit->toPlainText().left(textCursor.position()); + qDebug() << "leftOfCursor" << leftOfCursor; + + // RegEx [3] [4] + // (Module.subModule).(property/subModule) + + const int MODULE_INDEX = 3; + const int PROPERTY_INDEX = 4; + // TODO: disallow invalid characters on left of property + QRegExp regExp("((([A-Za-z0-9_\\.]+)\\.)|(?!\\.))([a-zA-Z0-9_]*)$"); + int pos = regExp.indexIn(leftOfCursor); + auto rexExpCapturedTexts = regExp.capturedTexts(); + auto memberOf = rexExpCapturedTexts[MODULE_INDEX]; + completionPrefix = rexExpCapturedTexts[PROPERTY_INDEX]; + bool switchedModule = false; + if (memberOf != _completerModule) { + _completerModule = memberOf; + auto autoCompleteModel = getAutoCompleteModel(memberOf); + if (autoCompleteModel == nullptr) { + _completer->popup()->hide(); + return false; + } + _completer->setModel(autoCompleteModel); + _completer->popup()->installEventFilter(this); + switchedModule = true; + } + + if (switchedModule || completionPrefix != _completer->completionPrefix()) { + _completer->setCompletionPrefix(completionPrefix); + qDebug() << "Set completion prefix to:" << completionPrefix; + _completer->popup()->setCurrentIndex(_completer->completionModel()->index(0, 0)); + } + auto cursorRect = _ui->promptTextEdit->cursorRect(); + cursorRect.setWidth(_completer->popup()->sizeHintForColumn(0) + + _completer->popup()->verticalScrollBar()->sizeHint().width()); + _completer->complete(cursorRect); + return false; + } } return false; } @@ -321,7 +498,7 @@ void JSConsole::appendMessage(const QString& gutter, const QString& message) { void JSConsole::clear() { QLayoutItem* item; - while ((item = _ui->logArea->layout()->takeAt(0)) != NULL) { + while ((item = _ui->logArea->layout()->takeAt(0)) != nullptr) { delete item->widget(); delete item; } diff --git a/interface/src/ui/JSConsole.h b/interface/src/ui/JSConsole.h index 4b6409a76f..eeb3601886 100644 --- a/interface/src/ui/JSConsole.h +++ b/interface/src/ui/JSConsole.h @@ -12,12 +12,11 @@ #ifndef hifi_JSConsole_h #define hifi_JSConsole_h -#include -#include #include #include -#include #include +#include +#include #include "ui_console.h" #include "ScriptEngine.h" @@ -54,12 +53,20 @@ protected slots: void handleError(const QString& message, const QString& scriptName); void commandFinished(); +private slots: + void insertCompletion(const QModelIndex& completion); + void highlightedCompletion(const QModelIndex& completion); + private: void appendMessage(const QString& gutter, const QString& message); void setToNextCommandInHistory(); void setToPreviousCommandInHistory(); void resetCurrentCommandHistory(); + void readAPI(); + + QStandardItemModel* getAutoCompleteModel(const QString& memberOf = nullptr); + QFutureWatcher _executeWatcher; Ui::Console* _ui; int _currentCommandInHistory; @@ -68,6 +75,9 @@ private: QString _rootCommand; ScriptEnginePointer _scriptEngine; static const QString _consoleFileName; + QJsonArray _apiDocs; + QCompleter* _completer; + QString _completerModule {""}; }; diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 2d33bbca14..a4e741a797 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -150,7 +150,7 @@ signals: /**jsdoc * Notifies scripts that a user has disconnected from the domain - * @function Users.avatar.avatarDisconnected + * @function Users.avatarDisconnected * @param {nodeID} NodeID The session ID of the avatar that has disconnected */ void avatarDisconnected(const QUuid& nodeID); diff --git a/tools/jsdoc/config.json b/tools/jsdoc/config.json index 0fb833d015..a24e248661 100644 --- a/tools/jsdoc/config.json +++ b/tools/jsdoc/config.json @@ -4,5 +4,8 @@ "outputSourceFiles": false } }, - "plugins": ["plugins/hifi"] + "plugins": [ + "plugins/hifi", + "plugins/hifiJSONExport" + ] } diff --git a/tools/jsdoc/package.json b/tools/jsdoc/package.json new file mode 100644 index 0000000000..215ceec177 --- /dev/null +++ b/tools/jsdoc/package.json @@ -0,0 +1,7 @@ +{ + "name": "hifiJSDoc", + "dependencies": { + "jsdoc": "^3.5.5" + }, + "private": true +} diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 3af5fbeee3..afa3285c37 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -47,5 +47,13 @@ exports.handlers = { } }); }); + + fs.writeFile("out/hifiJSDoc.js", e.source, function(err) { + if (err) { + return console.log(err); + } + + console.log("The Hifi JSDoc JS was saved!"); + }); } }; diff --git a/tools/jsdoc/plugins/hifiJSONExport.js b/tools/jsdoc/plugins/hifiJSONExport.js new file mode 100644 index 0000000000..7948fd2673 --- /dev/null +++ b/tools/jsdoc/plugins/hifiJSONExport.js @@ -0,0 +1,14 @@ +exports.handlers = { + processingComplete: function(e) { + var doclets = e.doclets.map(doclet => Object.assign({}, doclet)); + const fs = require('fs'); + doclets.map(doclet => {delete doclet.meta; delete doclet.comment}); + fs.writeFile("out/hifiJSDoc.json", JSON.stringify(doclets, null, 4), function(err) { + if (err) { + return console.log(err); + } + + console.log("The Hifi JSDoc JSON was saved!"); + }); + } +}; From a31fe7546ad2500a2544fc694697a92634c3c8bd Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 19 Jan 2018 19:45:25 +0100 Subject: [PATCH 02/15] - remove debug - fix API export JSON filename - fix warnings --- interface/src/ui/JSConsole.cpp | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index 7f68a205e6..3100b47882 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -63,7 +63,7 @@ void _writeLines(const QString& filename, const QList& lines) { } void JSConsole::readAPI() { - QFile file(PathUtils::resourcesPath() + "auto-complete/export.json"); + QFile file(PathUtils::resourcesPath() + "auto-complete/hifiJSDoc.json"); file.open(QFile::ReadOnly); auto json = QTextStream(&file).readAll().toUtf8(); _apiDocs = QJsonDocument::fromJson(json).array(); @@ -302,18 +302,12 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { insertCompletion(_completer->currentIndex()); _completer->popup()->hide(); return true; - case Qt::Key_Escape: - case Qt::Key_Tab: - case Qt::Key_Backtab: - qDebug() << "test"; - keyEvent->ignore();//setAccepted(false); - return false; // let the completer do default behavior default: return false; } } - if ((key == Qt::Key_Return || key == Qt::Key_Enter)) { + if (key == Qt::Key_Return || key == Qt::Key_Enter) { if (keyEvent->modifiers() & Qt::ShiftModifier) { // If the shift key is being used then treat it as a regular return/enter. If this isn't done, // a new QTextBlock isn't created. @@ -361,15 +355,11 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { } static QString eow("~!@#$%^&*()+{}|:\"<>?,/;'[]\\-="); // end of word - bool hasModifier = (keyEvent->modifiers() != Qt::NoModifier) && !ctrlOrShift; - if (!isCompleterShortcut && (!keyEvent->text().isEmpty() && eow.contains(keyEvent->text().right(1)))) { - qDebug() << "eow contains " << keyEvent->text().right(1) << " full text: " << keyEvent->text(); _completer->popup()->hide(); return false; } - qDebug() << "auto completing"; auto textCursor = _ui->promptTextEdit->textCursor(); @@ -378,7 +368,6 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { QString completionPrefix = textCursor.selectedText(); auto leftOfCursor = _ui->promptTextEdit->toPlainText().left(textCursor.position()); - qDebug() << "leftOfCursor" << leftOfCursor; // RegEx [3] [4] // (Module.subModule).(property/subModule) @@ -387,7 +376,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { const int PROPERTY_INDEX = 4; // TODO: disallow invalid characters on left of property QRegExp regExp("((([A-Za-z0-9_\\.]+)\\.)|(?!\\.))([a-zA-Z0-9_]*)$"); - int pos = regExp.indexIn(leftOfCursor); + regExp.indexIn(leftOfCursor); auto rexExpCapturedTexts = regExp.capturedTexts(); auto memberOf = rexExpCapturedTexts[MODULE_INDEX]; completionPrefix = rexExpCapturedTexts[PROPERTY_INDEX]; @@ -399,14 +388,13 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { _completer->popup()->hide(); return false; } - _completer->setModel(autoCompleteModel); + _completer->setModel(autoCompleteModel); _completer->popup()->installEventFilter(this); switchedModule = true; } if (switchedModule || completionPrefix != _completer->completionPrefix()) { _completer->setCompletionPrefix(completionPrefix); - qDebug() << "Set completion prefix to:" << completionPrefix; _completer->popup()->setCurrentIndex(_completer->completionModel()->index(0, 0)); } auto cursorRect = _ui->promptTextEdit->cursorRect(); From 483074b11e2dfd80f144e74aa0287c623c7d401d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Jan 2018 17:59:01 +0100 Subject: [PATCH 03/15] jsdoc cmake fixes --- interface/CMakeLists.txt | 19 +++++++++---------- tools/CMakeLists.txt | 4 +++- tools/jsdoc/CMakeLists.txt | 14 ++++++++++++++ tools/jsdoc/plugins/hifi.js | 9 ++++++--- tools/jsdoc/plugins/hifiJSONExport.js | 4 +++- 5 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 tools/jsdoc/CMakeLists.txt diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index fbc40f70c2..07788e5865 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -279,6 +279,9 @@ endif(UNIX) # assume we are using a Qt build without bearer management add_definitions(-DQT_NO_BEARERMANAGEMENT) +# require JSDoc to be build before interface is deployed (Console Auto-complete) +add_dependencies(${TARGET_NAME} jsdoc) + if (APPLE) # link in required OS X frameworks and include the right GL headers find_library(OpenGL OpenGL) @@ -299,6 +302,9 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/../Resources/scripts" + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" + "$/../Resources/auto-complete" ) # call the fixup_interface macro to add required bundling commands for installation @@ -313,6 +319,9 @@ else (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/scripts" + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" + "$/resources/auto-complete" ) # link target to external libraries @@ -349,16 +358,6 @@ endif() add_bugsplat() -# generate the JSDoc JSON for the JSConsole auto-completer -add_custom_command(TARGET ${TARGET_NAME} #POST_BUILD - COMMAND npm install - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/jsdoc -) -add_custom_command(TARGET ${TARGET_NAME} - COMMAND node_modules/.bin/jsdoc . -c config.json - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tools/jsdoc -) - if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 16446c5071..ac920d930d 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,6 +2,9 @@ add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") +add_subdirectory(jsdoc) +set_target_properties(jsdoc PROPERTIES FOLDER "Tools") + if (BUILD_TOOLS) add_subdirectory(udt-test) set_target_properties(udt-test PROPERTIES FOLDER "Tools") @@ -27,4 +30,3 @@ if (BUILD_TOOLS) add_subdirectory(auto-tester) set_target_properties(auto-tester PROPERTIES FOLDER "Tools") endif() - diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt new file mode 100644 index 0000000000..9a9883a6ad --- /dev/null +++ b/tools/jsdoc/CMakeLists.txt @@ -0,0 +1,14 @@ +set(TARGET_NAME jsdoc) + +add_custom_target(${TARGET_NAME}) + +SET(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) + +add_custom_command(TARGET ${TARGET_NAME} + COMMAND (npm --no-progress install) & (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH}) + WORKING_DIRECTORY ${JSDOC_WORKING_DIR} + COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" +) diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index afa3285c37..c434ecc0d6 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -10,6 +10,8 @@ function endsWith(path, exts) { exports.handlers = { beforeParse: function(e) { + const pathTools = require('path'); + var rootFolder = pathTools.dirname(e.filename); console.log("Scanning hifi source for jsdoc comments..."); // directories to scan for jsdoc comments @@ -34,9 +36,10 @@ exports.handlers = { const fs = require('fs'); dirList.forEach(function (dir) { - var files = fs.readdirSync(dir) + var joinedDir = pathTools.join(rootFolder, dir); + var files = fs.readdirSync(joinedDir) files.forEach(function (file) { - var path = dir + "/" + file; + var path = pathTools.join(joinedDir, file); if (fs.lstatSync(path).isFile() && endsWith(path, exts)) { var data = fs.readFileSync(path, "utf8"); var reg = /(\/\*\*jsdoc(.|[\r\n])*?\*\/)/gm; @@ -48,7 +51,7 @@ exports.handlers = { }); }); - fs.writeFile("out/hifiJSDoc.js", e.source, function(err) { + fs.writeFile(pathTools.join(rootFolder, "out/hifiJSDoc.js"), e.source, function(err) { if (err) { return console.log(err); } diff --git a/tools/jsdoc/plugins/hifiJSONExport.js b/tools/jsdoc/plugins/hifiJSONExport.js index 7948fd2673..5531d3ff25 100644 --- a/tools/jsdoc/plugins/hifiJSONExport.js +++ b/tools/jsdoc/plugins/hifiJSONExport.js @@ -1,9 +1,11 @@ exports.handlers = { processingComplete: function(e) { + const pathTools = require('path'); + var rootFolder = pathTools.join(__dirname, '..'); var doclets = e.doclets.map(doclet => Object.assign({}, doclet)); const fs = require('fs'); doclets.map(doclet => {delete doclet.meta; delete doclet.comment}); - fs.writeFile("out/hifiJSDoc.json", JSON.stringify(doclets, null, 4), function(err) { + fs.writeFile(pathTools.join(rootFolder, "out/hifiJSDoc.json"), JSON.stringify(doclets, null, 4), function(err) { if (err) { return console.log(err); } From 58216b947c1de891a6b948000f9030412e0e2f09 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Jan 2018 18:54:24 +0100 Subject: [PATCH 04/15] removal of unnecessary JS export (for now) --- tools/jsdoc/plugins/hifi.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index c434ecc0d6..bb556814e8 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -50,13 +50,5 @@ exports.handlers = { } }); }); - - fs.writeFile(pathTools.join(rootFolder, "out/hifiJSDoc.js"), e.source, function(err) { - if (err) { - return console.log(err); - } - - console.log("The Hifi JSDoc JS was saved!"); - }); } }; From 4ff3c9e0468ca69daf67bc86016100ac238e9beb Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Jan 2018 19:00:45 +0100 Subject: [PATCH 05/15] fix jsdoc output folder --- tools/jsdoc/CMakeLists.txt | 3 ++- tools/jsdoc/plugins/hifiJSONExport.js | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index 9a9883a6ad..265d8e9913 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -5,10 +5,11 @@ add_custom_target(${TARGET_NAME}) SET(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} - COMMAND (npm --no-progress install) & (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH}) + COMMAND (npm --no-progress install) & (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) WORKING_DIRECTORY ${JSDOC_WORKING_DIR} COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" ) diff --git a/tools/jsdoc/plugins/hifiJSONExport.js b/tools/jsdoc/plugins/hifiJSONExport.js index 5531d3ff25..cd14c9faad 100644 --- a/tools/jsdoc/plugins/hifiJSONExport.js +++ b/tools/jsdoc/plugins/hifiJSONExport.js @@ -1,11 +1,14 @@ exports.handlers = { processingComplete: function(e) { const pathTools = require('path'); - var rootFolder = pathTools.join(__dirname, '..'); + var outputFolder = pathTools.join(__dirname, '../out'); var doclets = e.doclets.map(doclet => Object.assign({}, doclet)); const fs = require('fs'); + if (!fs.existsSync(outputFolder)) { + fs.mkdirSync(outputFolder); + } doclets.map(doclet => {delete doclet.meta; delete doclet.comment}); - fs.writeFile(pathTools.join(rootFolder, "out/hifiJSDoc.json"), JSON.stringify(doclets, null, 4), function(err) { + fs.writeFile(pathTools.join(outputFolder, "hifiJSDoc.json"), JSON.stringify(doclets, null, 4), function(err) { if (err) { return console.log(err); } From f4e6606c8d407256686dd557a1bea82fc936ec5d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Jan 2018 19:01:13 +0100 Subject: [PATCH 06/15] fix MyAvatar JS API typo --- interface/src/avatar/MyAvatar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 58b49b61ff..086c76dd1a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -81,7 +81,7 @@ class MyAvatar : public Avatar { * and MyAvatar.customListenOrientation properties. * @property customListenPosition {Vec3} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the position * of audio spatialization listener. - * @property customListenOreintation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation + * @property customListenOrientation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation * of the audio spatialization listener. * @property audioListenerModeHead {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener * around the avatar's head. From 5885569644b4290e29c58e9aaa16175b0f923d3e Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Jan 2018 01:29:52 +0100 Subject: [PATCH 07/15] fix UNIX jsdoc command order --- tools/jsdoc/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index 265d8e9913..36c2af4265 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -9,7 +9,7 @@ file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} - COMMAND (npm --no-progress install) & (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) + COMMAND (npm --no-progress install) && (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) WORKING_DIRECTORY ${JSDOC_WORKING_DIR} COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" ) From 67effd1ad190ebdd0417591ac0e50fefe2031908 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Jan 2018 02:15:31 +0100 Subject: [PATCH 08/15] create folder --- interface/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 07788e5865..130848fc08 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -302,6 +302,8 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/../Resources/scripts" + COMMAND "${CMAKE_COMMAND}" -E make_directory + "$/../Resources/auto-complete" COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" "$/../Resources/auto-complete" @@ -319,6 +321,8 @@ else (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/scripts" + COMMAND "${CMAKE_COMMAND}" -E make_directory + "$/resources/auto-complete" COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" "$/resources/auto-complete" From 79dbde6a6ff07189c351c8c25639887e3bdc7f13 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Jan 2018 23:01:59 +0100 Subject: [PATCH 09/15] optional auto-complete build (requires NPM installed) --- cmake/macros/FindNPM.cmake | 14 ++++++++++++++ interface/CMakeLists.txt | 35 +++++++++++++++++++++++++---------- tools/CMakeLists.txt | 7 +++++-- tools/jsdoc/CMakeLists.txt | 4 +++- 4 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 cmake/macros/FindNPM.cmake diff --git a/cmake/macros/FindNPM.cmake b/cmake/macros/FindNPM.cmake new file mode 100644 index 0000000000..c66114f878 --- /dev/null +++ b/cmake/macros/FindNPM.cmake @@ -0,0 +1,14 @@ +# +# FindNPM.cmake +# cmake/macros +# +# Created by Thijs Wenker on 01/23/18. +# Copyright 2018 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# + +macro(find_npm) + find_program(NPM_EXECUTABLE "npm") +endmacro() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 130848fc08..b03a4637d0 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -41,6 +41,8 @@ endif() file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h") GroupSources("src") +find_npm() + # Add SpeechRecognizer if on Windows or OS X, otherwise remove if (WIN32) # Use .cpp and .h files as is. @@ -297,22 +299,38 @@ if (APPLE) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") + if (NPM_EXECUTABLE) + set(EXTRA_COPY_COMMANDS + COMMAND "${CMAKE_COMMAND}" -E make_directory + "$/../Resources/auto-complete" + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" + "$/../Resources/auto-complete" + ) + endif() + # copy script files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/../Resources/scripts" - COMMAND "${CMAKE_COMMAND}" -E make_directory - "$/../Resources/auto-complete" - COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" - "$/../Resources/auto-complete" + + ${EXTRA_COPY_COMMANDS} ) # call the fixup_interface macro to add required bundling commands for installation fixup_interface() else (APPLE) + if (NPM_EXECUTABLE) + set(EXTRA_COPY_COMMANDS + COMMAND "${CMAKE_COMMAND}" -E make_directory + "$/resources/auto-complete" + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" + "$/resources/auto-complete" + ) + endif() # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory @@ -321,11 +339,8 @@ else (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/scripts" - COMMAND "${CMAKE_COMMAND}" -E make_directory - "$/resources/auto-complete" - COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" - "$/resources/auto-complete" + + ${EXTRA_COPY_COMMANDS} ) # link target to external libraries diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ac920d930d..53d7fc2836 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,8 +2,11 @@ add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") -add_subdirectory(jsdoc) -set_target_properties(jsdoc PROPERTIES FOLDER "Tools") +find_npm() +if (NPM_EXECUTABLE) + add_subdirectory(jsdoc) + set_target_properties(jsdoc PROPERTIES FOLDER "Tools") +endif() if (BUILD_TOOLS) add_subdirectory(udt-test) diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index 36c2af4265..b52d229130 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -2,6 +2,8 @@ set(TARGET_NAME jsdoc) add_custom_target(${TARGET_NAME}) +find_npm() + SET(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) @@ -9,7 +11,7 @@ file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} - COMMAND (npm --no-progress install) && (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) + COMMAND (${NPM_EXECUTABLE} --no-progress install) && (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) WORKING_DIRECTORY ${JSDOC_WORKING_DIR} COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" ) From c46a1150006690ab8353bd888c0add5b55ec77cc Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 24 Jan 2018 01:45:53 +0100 Subject: [PATCH 10/15] attempt to fix ubuntu build --- tools/jsdoc/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index b52d229130..292523a813 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -4,14 +4,14 @@ add_custom_target(${TARGET_NAME}) find_npm() -SET(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) +set(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} - COMMAND (${NPM_EXECUTABLE} --no-progress install) && (${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR}) + COMMAND ${NPM_EXECUTABLE} --no-progress install && ${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR} WORKING_DIRECTORY ${JSDOC_WORKING_DIR} COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" ) From 179c21acf4b3c35752dfd648de52c47700fea982 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 25 Jan 2018 22:46:30 +0100 Subject: [PATCH 11/15] jsdoc info on selected auto-complete entry --- interface/src/ui/JSConsole.cpp | 145 ++++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 11 deletions(-) diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index 3100b47882..1ca1ac2842 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,21 @@ const QString RESULT_ERROR_STYLE = "color: #d13b22;"; const QString GUTTER_PREVIOUS_COMMAND = "<"; const QString GUTTER_ERROR = "X"; +const QString JSDOC_LINE_SEPARATOR = "\r"; + +const QString JSDOC_STYLE = + ""; + const QString JSConsole::_consoleFileName { "about:console" }; const QString JSON_KEY = "entries"; @@ -50,7 +66,7 @@ QList _readLines(const QString& filename) { // TODO: check root["version"] return root[JSON_KEY].toVariant().toStringList(); } - + void _writeLines(const QString& filename, const QList& lines) { QFile file(filename); file.open(QFile::WriteOnly); @@ -62,6 +78,10 @@ void _writeLines(const QString& filename, const QList& lines) { QTextStream(&file) << json; } +QString _jsdocTypeToString(QJsonValue jsdocType) { + return jsdocType.toObject().value("names").toVariant().toStringList().join("/"); +} + void JSConsole::readAPI() { QFile file(PathUtils::resourcesPath() + "auto-complete/hifiJSDoc.json"); file.open(QFile::ReadOnly); @@ -102,7 +122,10 @@ QStandardItemModel* JSConsole::getAutoCompleteModel(const QString& memberOf) { foreach(auto doc, _apiDocs) { auto object = doc.toObject(); auto scope = object.value("scope"); - if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") || (memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty)) { + if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") || + (memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty && + object.value("kind").toString() != "typedef")) { + model->appendRow(getAutoCompleteItem(doc)); } } @@ -166,20 +189,119 @@ JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) : } void JSConsole::insertCompletion(const QModelIndex& completion) { + auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject(); + auto kind = jsdocObject.value("kind").toString(); auto completionString = completion.data().toString(); - QTextCursor tc = _ui->promptTextEdit->textCursor(); + if (kind == "function") { + auto params = jsdocObject.value("params").toArray(); + // automatically add the parenthesis/parentheses for the functions + completionString += params.isEmpty() ? "()" : "("; + } + QTextCursor textCursor = _ui->promptTextEdit->textCursor(); int extra = completionString.length() - _completer->completionPrefix().length(); - tc.movePosition(QTextCursor::Left); - tc.movePosition(QTextCursor::EndOfWord); - tc.insertText(completionString.right(extra)); - _ui->promptTextEdit->setTextCursor(tc); + textCursor.movePosition(QTextCursor::Left); + textCursor.movePosition(QTextCursor::EndOfWord); + textCursor.insertText(completionString.right(extra)); + _ui->promptTextEdit->setTextCursor(textCursor); } void JSConsole::highlightedCompletion(const QModelIndex& completion) { - qDebug() << "Highlighted " << completion.data().toString(); auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject(); - - // qDebug() << "Highlighted data " << QJsonDocument(jsdocObject).toJson(QJsonDocument::Compact); + QString memberOf = ""; + if (!_completerModule.isEmpty()) { + memberOf = _completerModule + "."; + } + auto name = memberOf + "" + jsdocObject.value("name").toString() + ""; + auto description = jsdocObject.value("description").toString(); + auto examples = jsdocObject.value("examples").toArray(); + auto kind = jsdocObject.value("kind").toString(); + QString returnTypeText = ""; + + QString paramsTable = ""; + if (kind == "function") { + auto params = jsdocObject.value("params").toArray(); + auto returns = jsdocObject.value("returns"); + if (!returns.isUndefined()) { + returnTypeText = _jsdocTypeToString(jsdocObject.value("returns").toArray().at(0).toObject().value("type")) + " "; + } + name += "("; + if (!params.isEmpty()) { + bool hasDefaultParam = false; + bool hasOptionalParam = false; + bool firstItem = true; + foreach(auto param, params) { + auto paramObject = param.toObject(); + if (!hasOptionalParam && paramObject.value("optional").toBool(false)) { + hasOptionalParam = true; + name += "["; + } + if (!firstItem) { + name += ", "; + } else { + firstItem = false; + } + name += paramObject.value("name").toString(); + if (!hasDefaultParam && !paramObject.value("defaultvalue").isUndefined()) { + hasDefaultParam = true; + } + } + if (hasOptionalParam) { + name += "]"; + } + + paramsTable += ""; + if (hasDefaultParam) { + paramsTable += ""; + } + paramsTable += ""; + foreach(auto param, params) { + auto paramObject = param.toObject(); + paramsTable += ""; + } + + paramsTable += "
NameTypeDefaultDescription
" + paramObject.value("name").toString() + "" + + _jsdocTypeToString(paramObject.value("type")) + ""; + if (hasDefaultParam) { + paramsTable += paramObject.value("defaultvalue").toVariant().toString() + ""; + } + paramsTable += paramObject.value("description").toString() + "
"; + } + name += ")"; + } else if (!jsdocObject.value("type").isUndefined()){ + returnTypeText = _jsdocTypeToString(jsdocObject.value("type")) + " "; + } + auto popupText = JSDOC_STYLE + "" + returnTypeText + name + ""; + auto descriptionText = "

" + description.replace(JSDOC_LINE_SEPARATOR, "
") + "

"; + + popupText += descriptionText; + popupText += paramsTable; + auto returns = jsdocObject.value("returns"); + if (!returns.isUndefined()) { + foreach(auto returnEntry, returns.toArray()) { + auto returnsObject = returnEntry.toObject(); + auto returnsDescription = returnsObject.value("description").toString().replace(JSDOC_LINE_SEPARATOR, "
"); + popupText += "

Returns

" + returnsDescription + "

Type
" +
+                _jsdocTypeToString(returnsObject.value("type")) + "
"; + } + } + + if (!examples.isEmpty()) { + popupText += "

Examples

"; + foreach(auto example, examples) { + auto exampleText = example.toString(); + auto exampleLines = exampleText.split(JSDOC_LINE_SEPARATOR); + foreach(auto exampleLine, exampleLines) { + if (exampleLine.contains("")) { + popupText += exampleLine.replace("caption>", "h5>"); + } else { + popupText += "
" + exampleLine + "\n
"; + } + } + } + } + + QToolTip::showText(QPoint(_completer->popup()->pos().x() + _completer->popup()->width(), _completer->popup()->pos().y()), + popupText, _completer->popup()); } JSConsole::~JSConsole() { @@ -299,7 +421,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { case Qt::Key_Space: case Qt::Key_Enter: case Qt::Key_Return: - insertCompletion(_completer->currentIndex()); + insertCompletion(_completer->popup()->currentIndex()); _completer->popup()->hide(); return true; default: @@ -401,6 +523,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { cursorRect.setWidth(_completer->popup()->sizeHintForColumn(0) + _completer->popup()->verticalScrollBar()->sizeHint().width()); _completer->complete(cursorRect); + highlightedCompletion(_completer->popup()->currentIndex()); return false; } } From ce50380698285dde03ffccf639e795be51aeed9d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 25 Jan 2018 22:47:35 +0100 Subject: [PATCH 12/15] fix JSAPI typo --- libraries/entities/src/EntityScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 095a821482..9342f96e07 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -139,7 +139,7 @@ public slots: Q_INVOKABLE bool canRezTmpCertified(); /**jsdoc - * @function Entities.canWriteAsseets + * @function Entities.canWriteAssets * @return {bool} `true` if the DomainServer will allow this Node/Avatar to write to the asset server */ Q_INVOKABLE bool canWriteAssets(); From 2874a3d2cab9f46aa1dd9e4fbb1092f5029e7012 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 25 Jan 2018 23:39:32 +0100 Subject: [PATCH 13/15] fix android build (skip auto-complete when BUILD_TOOLS is FALSE) --- interface/CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 96d04c784d..d0d89919d5 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -276,8 +276,10 @@ endif() # assume we are using a Qt build without bearer management add_definitions(-DQT_NO_BEARERMANAGEMENT) -# require JSDoc to be build before interface is deployed (Console Auto-complete) -add_dependencies(${TARGET_NAME} jsdoc) +if (BUILD_TOOLS) + # require JSDoc to be build before interface is deployed (Console Auto-complete) + add_dependencies(${TARGET_NAME} jsdoc) +endif() if (APPLE) # link in required OS X frameworks and include the right GL headers @@ -294,7 +296,7 @@ if (APPLE) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") - if (NPM_EXECUTABLE) + if (BUILD_TOOLS AND NPM_EXECUTABLE) set(EXTRA_COPY_COMMANDS COMMAND "${CMAKE_COMMAND}" -E make_directory "$/../Resources/auto-complete" @@ -317,7 +319,7 @@ if (APPLE) fixup_interface() else() - if (NPM_EXECUTABLE) + if (BUILD_TOOLS AND NPM_EXECUTABLE) set(EXTRA_COPY_COMMANDS COMMAND "${CMAKE_COMMAND}" -E make_directory "$/resources/auto-complete" From 898c99e5b83a7b5315f626e152bcb82933daecb9 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 27 Jan 2018 19:50:52 +0100 Subject: [PATCH 14/15] append file jsdoc json file to resources.rcc resource file --- cmake/macros/GenerateQrc.cmake | 9 ++++++- interface/CMakeLists.txt | 49 +++++++++++++--------------------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/cmake/macros/GenerateQrc.cmake b/cmake/macros/GenerateQrc.cmake index 9bf530b2a2..5e2be71c82 100644 --- a/cmake/macros/GenerateQrc.cmake +++ b/cmake/macros/GenerateQrc.cmake @@ -1,7 +1,7 @@ function(GENERATE_QRC) set(oneValueArgs OUTPUT PREFIX PATH) - set(multiValueArgs GLOBS) + set(multiValueArgs CUSTOM_PATHS GLOBS) cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) if ("${GENERATE_QRC_PREFIX}" STREQUAL "") set(QRC_PREFIX_PATH /) @@ -20,6 +20,13 @@ function(GENERATE_QRC) endforeach() endforeach() + foreach(CUSTOM_PATH ${GENERATE_QRC_CUSTOM_PATHS}) + string(REPLACE "=" ";" CUSTOM_PATH ${CUSTOM_PATH}) + list(GET CUSTOM_PATH 0 IMPORT_PATH) + list(GET CUSTOM_PATH 1 LOCAL_PATH) + set(QRC_CONTENTS "${QRC_CONTENTS}${IMPORT_PATH}\n") + endforeach() + set(GENERATE_QRC_DEPENDS ${ALL_FILES} PARENT_SCOPE) configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT}) endfunction() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index d0d89919d5..fc3bc4a0d1 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -11,9 +11,22 @@ function(JOIN VALUES GLUE OUTPUT) set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE) endfunction() +set(CUSTOM_INTERFACE_QRC_PATHS "") + +function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH) + list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}") + set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE) +endfunction() + +find_npm() + +if (BUILD_TOOLS AND NPM_EXECUTABLE) + add_custom_qrc_path(CUSTOM_INTERFACE_QRC_PATHS "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" "auto-complete/hifiJSDoc.json") +endif () + set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc) -generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *) +generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_INTERFACE_QRC_PATHS} GLOBS *) add_custom_command( OUTPUT ${RESOURCES_RCC} @@ -53,8 +66,6 @@ file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h") GroupSources("src") list(APPEND INTERFACE_SRCS ${RESOURCES_RCC}) -find_npm() - # Add SpeechRecognizer if on Windows or OS X, otherwise remove if (WIN32) # Use .cpp and .h files as is. @@ -165,6 +176,11 @@ else () add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif () +if (BUILD_TOOLS AND NPM_EXECUTABLE) + # require JSDoc to be build before interface is deployed (Console Auto-complete) + add_dependencies(resources jsdoc) +endif() + add_dependencies(${TARGET_NAME} resources) if (WIN32) @@ -276,11 +292,6 @@ endif() # assume we are using a Qt build without bearer management add_definitions(-DQT_NO_BEARERMANAGEMENT) -if (BUILD_TOOLS) - # require JSDoc to be build before interface is deployed (Console Auto-complete) - add_dependencies(${TARGET_NAME} jsdoc) -endif() - if (APPLE) # link in required OS X frameworks and include the right GL headers find_library(OpenGL OpenGL) @@ -296,38 +307,18 @@ if (APPLE) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") - if (BUILD_TOOLS AND NPM_EXECUTABLE) - set(EXTRA_COPY_COMMANDS - COMMAND "${CMAKE_COMMAND}" -E make_directory - "$/../Resources/auto-complete" - COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" - "$/../Resources/auto-complete" - ) - endif() # copy script files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/../Resources/scripts" - - ${EXTRA_COPY_COMMANDS} ) # call the fixup_interface macro to add required bundling commands for installation fixup_interface() else() - if (BUILD_TOOLS AND NPM_EXECUTABLE) - set(EXTRA_COPY_COMMANDS - COMMAND "${CMAKE_COMMAND}" -E make_directory - "$/resources/auto-complete" - COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" - "$/resources/auto-complete" - ) - endif() # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different @@ -344,8 +335,6 @@ else() COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/scripts" - - ${EXTRA_COPY_COMMANDS} ) # link target to external libraries From 8fdbac521fc6b010248921dfb0d63bc2e6bee0a5 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 27 Jan 2018 19:58:15 +0100 Subject: [PATCH 15/15] move ADD_CUSTOM_QRC_PATH function to its own file --- cmake/macros/AddCustomQrcPath.cmake | 7 +++++++ interface/CMakeLists.txt | 5 ----- 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 cmake/macros/AddCustomQrcPath.cmake diff --git a/cmake/macros/AddCustomQrcPath.cmake b/cmake/macros/AddCustomQrcPath.cmake new file mode 100644 index 0000000000..6bd54baa4d --- /dev/null +++ b/cmake/macros/AddCustomQrcPath.cmake @@ -0,0 +1,7 @@ +# adds a custom path and local path to the inserted CUSTOM_PATHS_VAR list which +# can be given to the GENERATE_QRC command + +function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH) + list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}") + set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE) +endfunction() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index fc3bc4a0d1..a49019e775 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -13,11 +13,6 @@ endfunction() set(CUSTOM_INTERFACE_QRC_PATHS "") -function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH) - list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}") - set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE) -endfunction() - find_npm() if (BUILD_TOOLS AND NPM_EXECUTABLE)