Permalink
Browse files

Automate lazy generation of REPL completion lists

All invokable methods, slots, signals, and properties visible from
JavaScript, but which do not start with an underscore, are lazily
(only when necessary) added to the completion list through dynamic
reflection.

This leverages `QMetaObject` for reflection of `QObject`s.  As such,
there is now no need to inherit `REPLCompletable` and it has been
removed.

http://code.google.com/p/phantomjs/issues/detail?id=943
  • Loading branch information...
execjosh authored and ariya committed Dec 29, 2012
1 parent d0fe686 commit d906bc3819d54a9d643459cf06de7ac69cf55e53
Showing with 119 additions and 360 deletions.
  1. +2 −50 src/filesystem.cpp
  2. +2 −10 src/filesystem.h
  3. +1 −22 src/phantom.cpp
  4. +1 −3 src/phantom.h
  5. +2 −4 src/phantomjs.pro
  6. +51 −0 src/repl.cpp
  7. +3 −0 src/repl.h
  8. +49 −13 src/repl.js
  9. +0 −86 src/replcompletable.cpp
  10. +0 −60 src/replcompletable.h
  11. +1 −14 src/system.cpp
  12. +1 −3 src/system.h
  13. +1 −56 src/webpage.cpp
  14. +1 −5 src/webpage.h
  15. +2 −25 src/webserver.cpp
  16. +2 −9 src/webserver.h
View
@@ -38,7 +38,7 @@
// File
// public:
File::File(QFile *openfile, QTextCodec *codec, QObject *parent) :
- REPLCompletable(parent),
+ QObject(parent),
m_file(openfile),
m_fileStream(0)
{
@@ -213,23 +213,11 @@ void File::close()
deleteLater();
}
-void File::initCompletions()
-{
- // Add completion for the Dynamic Properties of the 'file' object
- // functions
- addCompletion("read");
- addCompletion("write");
- addCompletion("readLine");
- addCompletion("writeLine");
- addCompletion("flush");
- addCompletion("close");
-}
-
// FileSystem
// public:
FileSystem::FileSystem(QObject *parent)
- : REPLCompletable(parent)
+ : QObject(parent)
{ }
// public slots:
@@ -487,39 +475,3 @@ bool FileSystem::_remove(const QString &path) const
bool FileSystem::_copy(const QString &source, const QString &destination) const {
return QFile(source).copy(destination);
}
-
-void FileSystem::initCompletions()
-{
- // Add completion for the Dynamic Properties of the 'fs' object
- // properties
- addCompletion("separator");
- addCompletion("workingDirectory");
- // functions
- addCompletion("list");
- addCompletion("absolute");
- addCompletion("readLink");
- addCompletion("exists");
- addCompletion("isDirectory");
- addCompletion("isFile");
- addCompletion("isAbsolute");
- addCompletion("isExecutable");
- addCompletion("isReadable");
- addCompletion("isWritable");
- addCompletion("isLink");
- addCompletion("changeWorkingDirectory");
- addCompletion("makeDirectory");
- addCompletion("makeTree");
- addCompletion("removeDirectory");
- addCompletion("removeTree");
- addCompletion("copyTree");
- addCompletion("open");
- addCompletion("read");
- addCompletion("write");
- addCompletion("size");
- addCompletion("remove");
- addCompletion("copy");
- addCompletion("move");
- addCompletion("touch");
- addCompletion("join");
- addCompletion("split");
-}
View
@@ -36,9 +36,7 @@
#include <QTextStream>
#include <QVariant>
-#include "replcompletable.h"
-
-class File : public REPLCompletable
+class File : public QObject
{
Q_OBJECT
@@ -66,16 +64,13 @@ public slots:
void flush();
void close();
-private:
- virtual void initCompletions();
-
private:
QFile *m_file;
QTextStream *m_fileStream;
};
-class FileSystem : public REPLCompletable
+class FileSystem : public QObject
{
Q_OBJECT
Q_PROPERTY(QString workingDirectory READ workingDirectory)
@@ -139,9 +134,6 @@ public slots:
bool isReadable(const QString &path) const;
bool isWritable(const QString &path) const;
bool isLink(const QString &path) const;
-
-private:
- virtual void initCompletions();
};
#endif // FILESYSTEM_H
View
@@ -54,7 +54,7 @@ static Phantom *phantomInstance = NULL;
// private:
Phantom::Phantom(QObject *parent)
- : REPLCompletable(parent)
+ : QObject(parent)
, m_terminated(false)
, m_returnValue(0)
, m_filesystem(0)
@@ -471,24 +471,3 @@ void Phantom::doExit(int code)
m_page = 0;
QApplication::instance()->exit(code);
}
-
-void Phantom::initCompletions()
-{
- // Add completion for the Dynamic Properties of the 'phantom' object
- // properties
- addCompletion("args");
- addCompletion("defaultPageSettings");
- addCompletion("libraryPath");
- addCompletion("outputEncoding");
- addCompletion("scriptName");
- addCompletion("version");
- addCompletion("cookiesEnabled");
- addCompletion("cookies");
- // functions
- addCompletion("exit");
- addCompletion("debugExit");
- addCompletion("injectJs");
- addCompletion("addCookie");
- addCompletion("deleteCookie");
- addCompletion("clearCookies");
-}
View
@@ -37,15 +37,14 @@
#include "filesystem.h"
#include "encoding.h"
#include "config.h"
-#include "replcompletable.h"
#include "system.h"
#include "childprocess.h"
class WebPage;
class CustomPage;
class WebServer;
-class Phantom: public REPLCompletable
+class Phantom : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList args READ args)
@@ -181,7 +180,6 @@ private slots:
private:
void doExit(int code);
- virtual void initCompletions();
Encoding m_scriptFileEnc;
WebPage *m_page;
View
@@ -26,8 +26,7 @@ HEADERS += csconverter.h \
encoding.h \
config.h \
childprocess.h \
- repl.h \
- replcompletable.h
+ repl.h
SOURCES += phantom.cpp \
callback.cpp \
@@ -45,8 +44,7 @@ SOURCES += phantom.cpp \
encoding.cpp \
config.cpp \
childprocess.cpp \
- repl.cpp \
- replcompletable.cpp
+ repl.cpp
OTHER_FILES += \
bootstrap.js \
View
@@ -33,6 +33,8 @@
#include <QTimer>
#include <QDir>
#include <QRegExp>
+#include <QMetaMethod>
+#include <QMetaProperty>
#include "consts.h"
#include "terminal.h"
@@ -78,6 +80,52 @@ REPL *REPL::getInstance(QWebFrame *webframe, Phantom *parent)
return singleton;
}
+QString REPL::_getClassName(QObject *obj) const
+{
+ const QMetaObject *meta = obj->metaObject();
+
+ return QString::fromLatin1(meta->className());
+}
+
+QStringList REPL::_enumerateCompletions(QObject *obj) const
+{
+ const QMetaObject *meta = obj->metaObject();
+ QMap<QString, bool> completions;
+
+ // List up slots, signals, and invokable methods
+ const int methodOffset = meta->methodOffset();
+ const int methodCount = meta->methodCount();
+ for (int i = methodOffset; i < methodCount; i++) {
+ const QString name = QString::fromLatin1(meta->method(i).signature());
+ // Ignore methods starting with underscores
+ if (name.startsWith('_')) {
+ continue;
+ }
+ // Keep only up to, but not including, first paren
+ const int cutoff = name.indexOf('(');
+ completions.insert((0 < cutoff ? name.left(cutoff) : name), true);
+ }
+
+ // List up properties
+ const int propertyOffset = meta->propertyOffset();
+ const int propertyCount = meta->propertyCount();
+ for (int i = propertyOffset; i < propertyCount; i++) {
+ const QMetaProperty prop = meta->property(i);
+ // Ignore non-scriptable properties
+ if (!prop.isScriptable()) {
+ continue;
+ }
+ const QString name = QString::fromLatin1(prop.name());
+ // Ignore properties starting with underscores
+ if (name.startsWith('_')) {
+ continue;
+ }
+ completions.insert(name, true);
+ }
+
+ return completions.uniqueKeys();
+}
+
// private:
REPL::REPL(QWebFrame *webframe, Phantom *parent)
: QObject(parent),
@@ -101,6 +149,9 @@ REPL::REPL(QWebFrame *webframe, Phantom *parent)
// Inject REPL utility functions
m_webframe->evaluateJavaScript(Utils::readResourceFileUtf8(":/repl.js"), QString("phantomjs://repl.js"));
+ // Add self to JavaScript world
+ m_webframe->addToJavaScriptWindowObject("_repl", this);
+
// Start the REPL's loop
QTimer::singleShot(0, this, SLOT(startLoop()));
}
View
@@ -58,6 +58,9 @@ class REPL: public QObject
static bool instanceExists();
static REPL *getInstance(QWebFrame *webframe = NULL, Phantom *parent = NULL);
+ Q_INVOKABLE QString _getClassName(QObject *obj) const;
+ Q_INVOKABLE QStringList _enumerateCompletions(QObject *obj) const;
+
private:
REPL(QWebFrame *webframe, Phantom *parent);
static void offerCompletion(const char *buf, linenoiseCompletions *lc);
View
@@ -31,6 +31,13 @@
var REPL = REPL || {};
+(function () {
+
+/**
+ * Cache to hold completions
+ */
+var _cache = {};
+
/**
* Return the Completions of the Object, applying the prefix
*
@@ -40,16 +47,45 @@ var REPL = REPL || {};
REPL._getCompletions = function (obj, prefix) {
var completions = [];
- // If the given object is "(REPL)Completable", just return it's completions
- if (obj._isCompletable && obj._isCompletable() === true) {
- completions = obj._getCompletions(prefix || "");
+ if (typeof prefix !== "string") {
+ prefix = "";
} else {
- // It's a JS Native Object: build the list of completions manually
- for (k in obj) {
- if (obj.hasOwnProperty(k) && k.indexOf(prefix || "") === 0) {
- completions.push(k);
+ prefix = prefix.trim();
+ }
+
+ try {
+ // Try to get `QObject` inherited class's name (throws exception if not
+ // inherited from `QObject`)
+ var className = _repl._getClassName(obj);
+
+ // Initialize completions for this class as needed
+ if (null == _cache[className]) {
+ _cache[className] = _repl._enumerateCompletions(obj);
+ }
+
+ var key = className;
+ if ("" !== prefix) {
+ key = "-" + prefix;
+ if (null == _cache[key]) {
+ // Filter out completions
+ var regexp = new RegExp("^" + prefix);
+ _cache[key] = _cache[className].filter(function (elm) {
+ return regexp.test(elm);
+ });
}
}
+ completions = _cache[key];
+ } catch (e) {
+ try {
+ Object.keys(obj).forEach(function (k) {
+ if (obj.hasOwnProperty(k) && k.indexOf(prefix) === 0) {
+ completions.push(k);
+ }
+ });
+ completions.sort();
+ } catch (e) {
+ // Ignore...
+ }
}
return completions;
@@ -67,14 +103,12 @@ REPL._expResStringifyReplacer = function (k, v) {
mock = {},
funcToStr = "[Function]";
- // If the result of the last evaluated expression is a REPLCompletable object
+ // If the result of the last evaluated expression is an object
if (k === "" //< only first level of recursive calls
- && REPL._lastEval
- && REPL._lastEval._isCompletable
- && REPL._lastEval._isCompletable() === true) {
+ && REPL._lastEval) {
- // Get all the completions for the REPLCompletable object we are going to pretty-print
- iarr = REPL._lastEval._getCompletions("");
+ // Get all the completions for the object we are going to pretty-print
+ iarr = REPL._getCompletions(REPL._lastEval);
for (i in iarr) {
if (typeof(v[iarr[i]]) !== "undefined") {
// add a reference to this "real" property into the mock object
@@ -96,3 +130,5 @@ REPL._expResStringifyReplacer = function (k, v) {
return v;
};
+
+})();
Oops, something went wrong.

0 comments on commit d906bc3

Please sign in to comment.