OSDN Git Service

QmlJS: Add CompletionContextFinder to allow context sensitive completion
authorChristian Kamm <christian.d.kamm@nokia.com>
Wed, 28 Apr 2010 11:28:36 +0000 (13:28 +0200)
committerChristian Kamm <christian.d.kamm@nokia.com>
Thu, 29 Apr 2010 14:05:24 +0000 (16:05 +0200)
The plan is to use this to reduce the number of applicable
completion options, for instance for enum completion or property
names.

Done-with: Erik Verbruggen

src/libs/qmljs/qmljs-lib.pri
src/libs/qmljs/qmljscompletioncontextfinder.cpp [new file with mode: 0644]
src/libs/qmljs/qmljscompletioncontextfinder.h [new file with mode: 0644]
src/libs/qmljs/qmljsindenter.cpp
src/libs/qmljs/qmljslineinfo.cpp
src/plugins/qmljseditor/qmljscodecompletion.cpp

index 7b5adad..5a9b622 100644 (file)
@@ -19,7 +19,8 @@ HEADERS += \
     $$PWD/qmljslink.h \
     $$PWD/qmljscheck.h \
     $$PWD/qmljsscopebuilder.h \
-    $$PWD/qmljslineinfo.h
+    $$PWD/qmljslineinfo.h \
+    $$PWD/qmljscompletioncontextfinder.h
 
 SOURCES += \
     $$PWD/qmljsbind.cpp \
@@ -30,7 +31,8 @@ SOURCES += \
     $$PWD/qmljslink.cpp \
     $$PWD/qmljscheck.cpp \
     $$PWD/qmljsscopebuilder.cpp \
-    $$PWD/qmljslineinfo.cpp
+    $$PWD/qmljslineinfo.cpp \
+    $$PWD/qmljscompletioncontextfinder.cpp
 
 contains(QT, gui) {
     SOURCES += $$PWD/qmljsindenter.cpp
diff --git a/src/libs/qmljs/qmljscompletioncontextfinder.cpp b/src/libs/qmljs/qmljscompletioncontextfinder.cpp
new file mode 100644 (file)
index 0000000..05ea7f1
--- /dev/null
@@ -0,0 +1,157 @@
+#include "qmljscompletioncontextfinder.h"
+
+#include <QtCore/QDebug>
+#include <QtGui/QTextDocument>
+
+using namespace QmlJS;
+
+/*
+    Saves and restores the state of the global linizer. This enables
+    backtracking.
+
+    Identical to the defines in qmljslineinfo.cpp
+*/
+#define YY_SAVE() LinizerState savedState = yyLinizerState
+#define YY_RESTORE() yyLinizerState = savedState
+
+CompletionContextFinder::CompletionContextFinder(const QTextCursor &cursor)
+    : m_cursor(cursor)
+{
+    QTextBlock lastBlock = cursor.block();
+    if (lastBlock.next().isValid())
+        lastBlock = lastBlock.next();
+    initialize(cursor.document()->begin(), lastBlock);
+
+    int startTokenIndex = yyLinizerState.tokens.size() - 1;
+    for (; startTokenIndex >= 0; --startTokenIndex) {
+        const Token &token = yyLinizerState.tokens.at(startTokenIndex);
+        if (token.end() <= cursor.positionInBlock())
+            break;
+    }
+
+    getQmlObjectTypeName(startTokenIndex);
+}
+
+void CompletionContextFinder::getQmlObjectTypeName(int startTokenIndex)
+{
+    YY_SAVE();
+
+    int tokenIndex = findOpeningBrace(startTokenIndex);
+    if (tokenIndex != -1) {
+        --tokenIndex;
+
+        // can be one of
+        // A.B on c.d
+        // A.B
+
+        bool identifierExpected = true;
+        int i = tokenIndex;
+        forever {
+            if (i < 0) {
+                if (!readLine())
+                    break;
+                else
+                    i = yyLinizerState.tokens.size() - 1;
+            }
+
+            const Token &token = yyLinizerState.tokens.at(i);
+            if (!identifierExpected && token.kind == Token::Dot) {
+                identifierExpected = true;
+            } else if (token.kind == Token::Identifier) {
+                const QString idText = yyLine->mid(token.begin(), token.length);
+                if (identifierExpected) {
+                    m_qmlObjectTypeName.prepend(idText);
+                    identifierExpected = false;
+                } else if (idText == QLatin1String("on")) {
+                    m_qmlObjectTypeName.clear();
+                    identifierExpected = true;
+                } else {
+                    break;
+                }
+            } else {
+                break;
+            }
+
+            --i;
+        }
+    }
+
+    YY_RESTORE();
+}
+
+QStringList CompletionContextFinder::qmlObjectTypeName() const
+{
+    return m_qmlObjectTypeName;
+}
+
+int CompletionContextFinder::findOpeningBrace(int startTokenIndex)
+{
+    YY_SAVE();
+
+    if (startTokenIndex == -1)
+        readLine();
+
+    Token::Kind nestedClosing;
+    int nestingCount = 0;
+
+    for (int i = 0; i < BigRoof; ++i) {
+        if (i != 0 || startTokenIndex == -1)
+            startTokenIndex = yyLinizerState.tokens.size() - 1;
+
+        for (int t = startTokenIndex; t >= 0; --t) {
+            const Token &token = yyLinizerState.tokens.at(t);
+            switch (token.kind) {
+            case Token::LeftBrace:
+                if (nestingCount > 0) {
+                    if (nestedClosing == Token::RightBrace)
+                        --nestingCount;
+                } else {
+                    return t;
+                }
+                break;
+            case Token::LeftParenthesis:
+                if (nestingCount > 0) {
+                    if (nestedClosing == Token::RightParenthesis)
+                        --nestingCount;
+                } else {
+                    YY_RESTORE();
+                    return -1;
+                }
+                break;
+            case Token::LeftBracket:
+                if (nestingCount > 0) {
+                    if (nestedClosing == Token::RightBracket)
+                        --nestingCount;
+                } else {
+                    YY_RESTORE();
+                    return -1;
+                }
+                break;
+
+            case Token::RightBrace:
+            case Token::RightParenthesis:
+            case Token::RightBracket:
+                if (nestingCount == 0) {
+                    nestedClosing = token.kind;
+                    nestingCount = 1;
+                } else if (nestedClosing == token.kind) {
+                    ++nestingCount;
+                }
+                break;
+
+            default: break;
+            }
+        }
+
+        if (! readLine())
+            break;
+    }
+
+    YY_RESTORE();
+    return -1;
+}
+
+bool CompletionContextFinder::inQmlBindingRhs()
+{
+    return false;
+}
diff --git a/src/libs/qmljs/qmljscompletioncontextfinder.h b/src/libs/qmljs/qmljscompletioncontextfinder.h
new file mode 100644 (file)
index 0000000..0f912d1
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef QMLJSCOMPLETIONCONTEXTFINDER_H
+#define QMLJSCOMPLETIONCONTEXTFINDER_H
+
+#include "qmljs_global.h"
+#include <qmljs/qmljslineinfo.h>
+
+#include <QtCore/QStringList>
+#include <QtGui/QTextCursor>
+
+namespace QmlJS {
+
+class QMLJS_EXPORT CompletionContextFinder : public LineInfo
+{
+public:
+    CompletionContextFinder(const QTextCursor &cursor);
+
+    //bool inQmlObjectDefinition();
+    bool inQmlBindingRhs();
+
+    QStringList qmlObjectTypeName() const;
+
+private:
+    int findOpeningBrace(int startTokenIndex);
+    void getQmlObjectTypeName(int startTokenIndex);
+
+    QTextCursor m_cursor;
+    QStringList m_qmlObjectTypeName;
+};
+
+} // namespace QmlJS
+
+#endif // QMLJSCOMPLETIONCONTEXTFINDER_H
index 9162f4d..49b95eb 100644 (file)
@@ -569,14 +569,10 @@ int QmlJSIndenter::indentForStandaloneLine()
 */
 int QmlJSIndenter::indentForBottomLine(QTextBlock begin, QTextBlock end, QChar typedIn)
 {
-    if (begin == end)
-        return 0;
-
-    yyProgram = Program(begin, end);
-    startLinizer();
-
     const QTextBlock last = end.previous();
 
+    initialize(begin, last);
+
     QString bottomLine = last.text();
     QChar firstCh = firstNonWhiteSpace(bottomLine);
     int indent = 0;
index 17113a3..b43dc66 100644 (file)
@@ -339,7 +339,6 @@ void LineInfo::startLinizer()
     yyLeftBraceFollows = &yyLinizerState.leftBraceFollows;
 
     yyLinizerState.iter = yyProgram.lastBlock();
-    yyLinizerState.iter = yyLinizerState.iter.previous();
     yyLinizerState.line = yyLinizerState.iter.text();
     readLine();
 }
index fe7eeb8..6ef1bc3 100644 (file)
@@ -37,6 +37,7 @@
 #include <qmljs/qmljsinterpreter.h>
 #include <qmljs/qmljsscanner.h>
 #include <qmljs/qmljsevaluate.h>
+#include <qmljs/qmljscompletioncontextfinder.h>
 
 #include <texteditor/basetexteditor.h>
 
@@ -638,6 +639,12 @@ int CodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
 
     if (completionOperator.isSpace() || completionOperator.isNull() || isDelimiter(completionOperator) ||
             (completionOperator == QLatin1Char('(') && m_startPosition != editor->position())) {
+/*
+        QTextCursor startPositionCursor(edit->document());
+        startPositionCursor.setPosition(m_startPosition);
+        CompletionContextFinder contextFinder(startPositionCursor);
+        qDebug() << "Qml type name" << contextFinder.qmlObjectTypeName();
+*/
         // It's a global completion.
         EnumerateProperties enumerateProperties(&context);
         enumerateProperties.setGlobalCompletion(true);