diff options
Diffstat (limited to 'src/scripttools/debugging/qscriptcompletiontask.cpp')
-rw-r--r-- | src/scripttools/debugging/qscriptcompletiontask.cpp | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/src/scripttools/debugging/qscriptcompletiontask.cpp b/src/scripttools/debugging/qscriptcompletiontask.cpp new file mode 100644 index 0000000..3767505 --- /dev/null +++ b/src/scripttools/debugging/qscriptcompletiontask.cpp @@ -0,0 +1,305 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtSCriptTools module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qscriptcompletiontask_p.h" +#include "qscriptcompletiontaskinterface_p_p.h" +#include "qscriptdebuggerconsole_p.h" +#include "qscriptdebuggerconsolecommand_p.h" +#include "qscriptdebuggerconsolecommandmanager_p.h" + +#include "qscriptenginedebuggerfrontend_p.h" // ### kill +#include "qscriptdebuggerbackend_p.h" // ### kill +#include <QtScript/qscriptcontext.h> +#include <QtScript/qscriptvalue.h> +#include <QtScript/qscriptvalueiterator.h> + +#include "private/qobject_p.h" + +#include <QtCore/qset.h> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +class QScriptCompletionTaskPrivate + : public QScriptCompletionTaskInterfacePrivate +{ + Q_DECLARE_PUBLIC(QScriptCompletionTask) +public: + QScriptCompletionTaskPrivate(); + ~QScriptCompletionTaskPrivate(); + + void completeScriptExpression(); + + QString contents; + int cursorPosition; + int frameIndex; + QScriptDebuggerFrontend *frontend; + QScriptDebuggerConsole *console; +}; + +QScriptCompletionTaskPrivate::QScriptCompletionTaskPrivate() +{ +} + +QScriptCompletionTaskPrivate::~QScriptCompletionTaskPrivate() +{ +} + +QScriptCompletionTask::QScriptCompletionTask( + const QString &contents, int cursorPosition, + int frameIndex, QScriptDebuggerFrontend *frontend, + QScriptDebuggerConsole *console, + QObject *parent) + : QScriptCompletionTaskInterface( + *new QScriptCompletionTaskPrivate, parent) +{ + Q_D(QScriptCompletionTask); + d->contents = contents; + d->cursorPosition = cursorPosition; + if ((frameIndex == -1) && console) + d->frameIndex = console->currentFrameIndex(); + else + d->frameIndex = frameIndex; + d->frontend = frontend; + d->console = console; +} + +QScriptCompletionTask::~QScriptCompletionTask() +{ +} + +namespace { + +static bool isIdentChar(const QChar &ch) +{ + static QChar underscore = QLatin1Char('_'); + return ch.isLetter() || (ch == underscore); +} + +static bool isPrefixOf(const QString &prefix, const QString &what) +{ + return ((what.length() > prefix.length()) + && what.startsWith(prefix)); +} + +} // namespace + +void QScriptCompletionTaskPrivate::completeScriptExpression() +{ + int pos = cursorPosition; + if ((pos > 0) && contents.at(pos-1).isNumber()) { + // completion of numbers is pointless + return; + } + + while ((pos > 0) && isIdentChar(contents.at(pos-1))) + --pos; + int pos2 = cursorPosition; + while ((pos2 < contents.size()-1) && isIdentChar(contents.at(pos2+1))) + ++pos2; + QString ident = contents.mid(pos, pos2 - pos + 1); + position = pos; + + QStringList path; + path.append(ident); + while ((pos > 0) && (contents.at(pos-1) == QLatin1Char('.'))) { + --pos; + pos2 = pos; + while ((pos > 0) && isIdentChar(contents.at(pos-1))) + --pos; + path.prepend(contents.mid(pos, pos2 - pos)); + } + + // ### super-cheating for now; have to use the async API + QScriptEngineDebuggerFrontend *edf = static_cast<QScriptEngineDebuggerFrontend*>(frontend); + QScriptDebuggerBackend *backend = edf->backend(); + QScriptContext *ctx = backend->context(frameIndex); + QScriptValueList objects; + QString prefix = path.last(); + QSet<QString> matches; + if (path.size() > 1) { + const QString &topLevelIdent = path.at(0); + QScriptValue obj; + if (topLevelIdent == QString::fromLatin1("this")) { + obj = ctx->thisObject(); + } else { + QScriptValueList scopeChain; +#if QT_VERSION >= 0x040500 + scopeChain = ctx->scopeChain(); +#else + scopeChain.append(ctx->activationObject()); +#endif + for (int i = 0; i < scopeChain.size(); ++i) { + QScriptValue oo = scopeChain.at(i).property(topLevelIdent); + if (oo.isObject()) { + obj = oo; + break; + } + } + } + for (int i = 1; obj.isObject() && (i < path.size()-1); ++i) + obj = obj.property(path.at(i)); + if (obj.isValid()) + objects.append(obj); + } else { +#if QT_VERSION >= 0x040500 + objects << ctx->scopeChain(); +#else + objects.append(ctx->activationObject()); +#endif + QStringList keywords; + keywords.append(QString::fromLatin1("this")); + keywords.append(QString::fromLatin1("true")); + keywords.append(QString::fromLatin1("false")); + keywords.append(QString::fromLatin1("null")); + for (int i = 0; i < keywords.size(); ++i) { + const QString &kwd = keywords.at(i); + if (isPrefixOf(prefix, kwd)) + matches.insert(kwd); + } + } + + for (int i = 0; i < objects.size(); ++i) { + QScriptValue obj = objects.at(i); + while (obj.isObject()) { + QScriptValueIterator it(obj); + while (it.hasNext()) { + it.next(); + QString propertyName = it.name(); + if (isPrefixOf(prefix, propertyName)) + matches.insert(propertyName); + } + obj = obj.prototype(); + } + } + results = matches.toList(); + qStableSort(results); + + length = prefix.length(); + type = QScriptCompletionTask::ScriptIdentifierCompletion; +} + +void QScriptCompletionTask::start() +{ + Q_D(QScriptCompletionTask); + d->type = NoCompletion; + // see if we're typing a command + // ### don't hardcode the command prefix + QRegExp cmdRx(QString::fromLatin1("^\\s*\\.([a-zA-Z]*)")); + int cmdIndex = cmdRx.indexIn(d->contents); + if ((cmdIndex != -1) && d->console) { + int len = cmdRx.matchedLength(); + QString prefix = cmdRx.capturedTexts().at(1); + if ((d->cursorPosition >= cmdIndex) && (d->cursorPosition <= (cmdIndex+len))) { + // editing command --> get command completions + d->results = d->console->commandManager()->completions(prefix); + qStableSort(d->results); + d->position = cmdRx.pos(1); + d->length = prefix.length(); + d->type = CommandNameCompletion; + d->appendix = QString::fromLatin1(" "); + emit finished(); + } else { + QScriptDebuggerConsoleCommand *cmd = d->console->commandManager()->findCommand(prefix); + if (!cmd) { + emit finished(); + return; + } + // editing an argument + int argNum = 0; + QString arg; + int pos = cmdIndex + len; + while (pos < d->contents.size()) { + while ((pos < d->contents.size()) && d->contents.at(pos).isSpace()) + ++pos; + if (pos < d->contents.size()) { + int pos2 = pos + 1; + while ((pos2 < d->contents.size()) && !d->contents.at(pos2).isSpace()) + ++pos2; + if ((d->cursorPosition >= pos) && (d->cursorPosition <= pos2)) { + arg = d->contents.mid(pos, pos2 - pos); + break; + } + pos = pos2; + ++argNum; + } + } + QString argType = cmd->argumentTypes().value(argNum); + if (!argType.isEmpty()) { + if (argType == QString::fromLatin1("command-or-group-name")) { + d->results = d->console->commandManager()->completions(arg); + } else if (argType == QString::fromLatin1("script-filename")) { + // ### super-cheating for now; have to use the async API + QScriptEngineDebuggerFrontend *edf = static_cast<QScriptEngineDebuggerFrontend*>(d->frontend); + QScriptDebuggerBackend *backend = edf->backend(); + QScriptScriptMap scripts = backend->scripts(); + QScriptScriptMap::const_iterator it; + for (it = scripts.constBegin(); it != scripts.constEnd(); ++it) { + QString fileName = it.value().fileName(); + if (isPrefixOf(arg, fileName)) + d->results.append(fileName); + } + } else if (argType == QString::fromLatin1("subcommand-name")) { + for (int i = 0; i < cmd->subCommands().size(); ++i) { + QString name = cmd->subCommands().at(i); + if (isPrefixOf(arg, name)) + d->results.append(name); + } + } else if (argType == QString::fromLatin1("script")) { + d->completeScriptExpression(); + } + if ((d->type == NoCompletion) && !d->results.isEmpty()) { + qStableSort(d->results); + d->position = pos; + d->length = arg.length(); + d->type = CommandArgumentCompletion; + } + } + emit finished(); + } + } else { + // assume it's an eval expression + d->completeScriptExpression(); + emit finished(); + } +} + +QT_END_NAMESPACE |