Showing the changes to interface/src/ui/JSConsole.cpp (1 of 1 file). Show all
Unified · Split
| @@ -17,6 +17,7 @@ · 16 unmodified lines | |||
| 17 | #include <QtConcurrent/QtConcurrentRun> | 17 | #include <QtConcurrent/QtConcurrentRun> |
| 18 | #include <QStringListModel> | 18 | #include <QStringListModel> |
| 19 | #include <QListView> | 19 | #include <QListView> |
| 20 | #include <QToolTip> | ||
| 20 | 21 | ||
| 21 | #include <shared/QtHelpers.h> | 22 | #include <shared/QtHelpers.h> |
| 22 | #include <ScriptEngines.h> | 23 | #include <ScriptEngines.h> |
| @@ -39,6 +40,21 @@ const QString RESULT_ERROR_STYLE = "color: #d13b22;"; · 16 unmodified lines | |||
| 39 | const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\"><</span>"; | 40 | const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\"><</span>"; |
| 40 | const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>"; | 41 | const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>"; |
| 41 | 42 | ||
| 43 | const QString JSDOC_LINE_SEPARATOR = "\r"; | ||
| 44 | |||
| 45 | const QString JSDOC_STYLE = | ||
| 46 | "<style type=\"text/css\"> \ | ||
| 47 | .code { \ | ||
| 48 | font-family: Consolas, Monaco, 'Andale Mono', monospace \ | ||
| 49 | } \ | ||
| 50 | pre, code { \ | ||
| 51 | display: inline; \ | ||
| 52 | } \ | ||
| 53 | .no-wrap { \ | ||
| 54 | white-space: nowrap; \ | ||
| 55 | } \ | ||
| 56 | </style>"; | ||
| 57 | |||
| 42 | const QString JSConsole::_consoleFileName { "about:console" }; | 58 | const QString JSConsole::_consoleFileName { "about:console" }; |
| 43 | 59 | ||
| 44 | const QString JSON_KEY = "entries"; | 60 | const QString JSON_KEY = "entries"; |
| @@ -50,7 +66,7 @@ QList<QString> _readLines(const QString& filename) { · 5 unmodified lines | |||
| 50 | // TODO: check root["version"] | 66 | // TODO: check root["version"] |
| 51 | return root[JSON_KEY].toVariant().toStringList(); | 67 | return root[JSON_KEY].toVariant().toStringList(); |
| 52 | } | 68 | } |
| 53 | 69 | ||
| 54 | void _writeLines(const QString& filename, const QList<QString>& lines) { | 70 | void _writeLines(const QString& filename, const QList<QString>& lines) { |
| 55 | QFile file(filename); | 71 | QFile file(filename); |
| 56 | file.open(QFile::WriteOnly); | 72 | file.open(QFile::WriteOnly); |
| @@ -62,6 +78,10 @@ void _writeLines(const QString& filename, const QList<QString>& lines) { · 5 unmodified lines | |||
| 62 | QTextStream(&file) << json; | 78 | QTextStream(&file) << json; |
| 63 | } | 79 | } |
| 64 | 80 | ||
| 81 | QString _jsdocTypeToString(QJsonValue jsdocType) { | ||
| 82 | return jsdocType.toObject().value("names").toVariant().toStringList().join("/"); | ||
| 83 | } | ||
| 84 | |||
| 65 | void JSConsole::readAPI() { | 85 | void JSConsole::readAPI() { |
| 66 | QFile file(PathUtils::resourcesPath() + "auto-complete/hifiJSDoc.json"); | 86 | QFile file(PathUtils::resourcesPath() + "auto-complete/hifiJSDoc.json"); |
| 67 | file.open(QFile::ReadOnly); | 87 | file.open(QFile::ReadOnly); |
| @@ -102,7 +122,10 @@ QStandardItemModel* JSConsole::getAutoCompleteModel(const QString& memberOf) { · 34 unmodified lines | |||
| 102 | foreach(auto doc, _apiDocs) { | 122 | foreach(auto doc, _apiDocs) { |
| 103 | auto object = doc.toObject(); | 123 | auto object = doc.toObject(); |
| 104 | auto scope = object.value("scope"); | 124 | auto scope = object.value("scope"); |
| 105 | if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") || (memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty)) { | 125 | if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") || |
| 126 | (memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty && | ||
| 127 | object.value("kind").toString() != "typedef")) { | ||
| 128 | |||
| 106 | model->appendRow(getAutoCompleteItem(doc)); | 129 | model->appendRow(getAutoCompleteItem(doc)); |
| 107 | } | 130 | } |
| 108 | } | 131 | } |
| @@ -166,20 +189,119 @@ JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) : · 57 unmodified lines | |||
| 166 | } | 189 | } |
| 167 | 190 | ||
| 168 | void JSConsole::insertCompletion(const QModelIndex& completion) { | 191 | void JSConsole::insertCompletion(const QModelIndex& completion) { |
| 192 | auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject(); | ||
| 193 | auto kind = jsdocObject.value("kind").toString(); | ||
| 169 | auto completionString = completion.data().toString(); | 194 | auto completionString = completion.data().toString(); |
| 170 | QTextCursor tc = _ui->promptTextEdit->textCursor(); | 195 | if (kind == "function") { |
| 196 | auto params = jsdocObject.value("params").toArray(); | ||
| 197 | // automatically add the parenthesis/parentheses for the functions | ||
| 198 | completionString += params.isEmpty() ? "()" : "("; | ||
| 199 | } | ||
| 200 | QTextCursor textCursor = _ui->promptTextEdit->textCursor(); | ||
| 171 | int extra = completionString.length() - _completer->completionPrefix().length(); | 201 | int extra = completionString.length() - _completer->completionPrefix().length(); |
| 172 | tc.movePosition(QTextCursor::Left); | 202 | textCursor.movePosition(QTextCursor::Left); |
| 173 | tc.movePosition(QTextCursor::EndOfWord); | 203 | textCursor.movePosition(QTextCursor::EndOfWord); |
| 174 | tc.insertText(completionString.right(extra)); | 204 | textCursor.insertText(completionString.right(extra)); |
| 175 | _ui->promptTextEdit->setTextCursor(tc); | 205 | _ui->promptTextEdit->setTextCursor(textCursor); |
| 176 | } | 206 | } |
| 177 | 207 | ||
| 178 | void JSConsole::highlightedCompletion(const QModelIndex& completion) { | 208 | void JSConsole::highlightedCompletion(const QModelIndex& completion) { |
| 179 | qDebug() << "Highlighted " << completion.data().toString(); | ||
| 180 | auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject(); | 209 | auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject(); |
| 181 | 210 | QString memberOf = ""; | |
| 182 | // qDebug() << "Highlighted data " << QJsonDocument(jsdocObject).toJson(QJsonDocument::Compact); | 211 | if (!_completerModule.isEmpty()) { |
| 212 | memberOf = _completerModule + "."; | ||
| 213 | } | ||
| 214 | auto name = memberOf + "<b>" + jsdocObject.value("name").toString() + "</b>"; | ||
| 215 | auto description = jsdocObject.value("description").toString(); | ||
| 216 | auto examples = jsdocObject.value("examples").toArray(); | ||
| 217 | auto kind = jsdocObject.value("kind").toString(); | ||
| 218 | QString returnTypeText = ""; | ||
| 219 | |||
| 220 | QString paramsTable = ""; | ||
| 221 | if (kind == "function") { | ||
| 222 | auto params = jsdocObject.value("params").toArray(); | ||
| 223 | auto returns = jsdocObject.value("returns"); | ||
| 224 | if (!returns.isUndefined()) { | ||
| 225 | returnTypeText = _jsdocTypeToString(jsdocObject.value("returns").toArray().at(0).toObject().value("type")) + " "; | ||
| 226 | } | ||
| 227 | name += "("; | ||
| 228 | if (!params.isEmpty()) { | ||
| 229 | bool hasDefaultParam = false; | ||
| 230 | bool hasOptionalParam = false; | ||
| 231 | bool firstItem = true; | ||
| 232 | foreach(auto param, params) { | ||
| 233 | auto paramObject = param.toObject(); | ||
| 234 | if (!hasOptionalParam && paramObject.value("optional").toBool(false)) { | ||
| 235 | hasOptionalParam = true; | ||
| 236 | name += "<i>["; | ||
| 237 | } | ||
| 238 | if (!firstItem) { | ||
| 239 | name += ", "; | ||
| 240 | } else { | ||
| 241 | firstItem = false; | ||
| 242 | } | ||
| 243 | name += paramObject.value("name").toString(); | ||
| 244 | if (!hasDefaultParam && !paramObject.value("defaultvalue").isUndefined()) { | ||
| 245 | hasDefaultParam = true; | ||
| 246 | } | ||
| 247 | } | ||
| 248 | if (hasOptionalParam) { | ||
| 249 | name += "]</i>"; | ||
| 250 | } | ||
| 251 | |||
| 252 | paramsTable += "<table border=\"1\" cellpadding=\"10\"><thead><tr><th>Name</th><th>Type</th>"; | ||
| 253 | if (hasDefaultParam) { | ||
| 254 | paramsTable += "<th>Default</th>"; | ||
| 255 | } | ||
| 256 | paramsTable += "<th>Description</th></tr></thead><tbody>"; | ||
| 257 | foreach(auto param, params) { | ||
| 258 | auto paramObject = param.toObject(); | ||
| 259 | paramsTable += "<tr><td>" + paramObject.value("name").toString() + "</td><td>" + | ||
| 260 | _jsdocTypeToString(paramObject.value("type")) + "</td><td>"; | ||
| 261 | if (hasDefaultParam) { | ||
| 262 | paramsTable += paramObject.value("defaultvalue").toVariant().toString() + "</td><td>"; | ||
| 263 | } | ||
| 264 | paramsTable += paramObject.value("description").toString() + "</td></tr>"; | ||
| 265 | } | ||
| 266 | |||
| 267 | paramsTable += "</tbody></table>"; | ||
| 268 | } | ||
| 269 | name += ")"; | ||
| 270 | } else if (!jsdocObject.value("type").isUndefined()){ | ||
| 271 | returnTypeText = _jsdocTypeToString(jsdocObject.value("type")) + " "; | ||
| 272 | } | ||
| 273 | auto popupText = JSDOC_STYLE + "<span class=\"no-wrap\">" + returnTypeText + name + "</span>"; | ||
| 274 | auto descriptionText = "<p>" + description.replace(JSDOC_LINE_SEPARATOR, "<br>") + "</p>"; | ||
| 275 | |||
| 276 | popupText += descriptionText; | ||
| 277 | popupText += paramsTable; | ||
| 278 | auto returns = jsdocObject.value("returns"); | ||
| 279 | if (!returns.isUndefined()) { | ||
| 280 | foreach(auto returnEntry, returns.toArray()) { | ||
| 281 | auto returnsObject = returnEntry.toObject(); | ||
| 282 | auto returnsDescription = returnsObject.value("description").toString().replace(JSDOC_LINE_SEPARATOR, "<br>"); | ||
| 283 | popupText += "<h4>Returns</h4><p>" + returnsDescription + "</p><h5>Type</h5><pre><code>" + | ||
| 284 | _jsdocTypeToString(returnsObject.value("type")) + "</code></pre>"; | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | if (!examples.isEmpty()) { | ||
| 289 | popupText += "<h4>Examples</h4>"; | ||
| 290 | foreach(auto example, examples) { | ||
| 291 | auto exampleText = example.toString(); | ||
| 292 | auto exampleLines = exampleText.split(JSDOC_LINE_SEPARATOR); | ||
| 293 | foreach(auto exampleLine, exampleLines) { | ||
| 294 | if (exampleLine.contains("<caption>")) { | ||
| 295 | popupText += exampleLine.replace("caption>", "h5>"); | ||
| 296 | } else { | ||
| 297 | popupText += "<pre><code>" + exampleLine + "\n</code></pre>"; | ||
| 298 | } | ||
| 299 | } | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | QToolTip::showText(QPoint(_completer->popup()->pos().x() + _completer->popup()->width(), _completer->popup()->pos().y()), | ||
| 304 | popupText, _completer->popup()); | ||
| 183 | } | 305 | } |
| 184 | 306 | ||
| 185 | JSConsole::~JSConsole() { | 307 | JSConsole::~JSConsole() { |
| @@ -299,7 +421,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { · 113 unmodified lines | |||
| 299 | case Qt::Key_Space: | 421 | case Qt::Key_Space: |
| 300 | case Qt::Key_Enter: | 422 | case Qt::Key_Enter: |
| 301 | case Qt::Key_Return: | 423 | case Qt::Key_Return: |
| 302 | insertCompletion(_completer->currentIndex()); | 424 | insertCompletion(_completer->popup()->currentIndex()); |
| 303 | _completer->popup()->hide(); | 425 | _completer->popup()->hide(); |
| 304 | return true; | 426 | return true; |
| 305 | default: | 427 | default: |
| @@ -401,6 +523,7 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { · 95 unmodified lines | |||
| 401 | cursorRect.setWidth(_completer->popup()->sizeHintForColumn(0) + | 523 | cursorRect.setWidth(_completer->popup()->sizeHintForColumn(0) + |
| 402 | _completer->popup()->verticalScrollBar()->sizeHint().width()); | 524 | _completer->popup()->verticalScrollBar()->sizeHint().width()); |
| 403 | _completer->complete(cursorRect); | 525 | _completer->complete(cursorRect); |
| 526 | highlightedCompletion(_completer->popup()->currentIndex()); | ||
| 404 | return false; | 527 | return false; |
| 405 | } | 528 | } |
| 406 | } | 529 | } |