QmlJS: Local-file completion for url properties.

Task-number: QTCREATORBUG-2619
Reviewed-by: Erik Verbruggen
This commit is contained in:
Christian Kamm
2010-11-11 16:08:32 +01:00
parent 83e7d7c350
commit fca62bbd5d
2 changed files with 99 additions and 27 deletions

View File

@@ -46,11 +46,13 @@
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <utils/faketooltip.h> #include <utils/faketooltip.h>
#include <utils/qtcassert.h>
#include <QtCore/QFile> #include <QtCore/QFile>
#include <QtCore/QFileInfo> #include <QtCore/QFileInfo>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QDirIterator>
#include <QtGui/QApplication> #include <QtGui/QApplication>
#include <QtGui/QDesktopWidget> #include <QtGui/QDesktopWidget>
@@ -520,6 +522,7 @@ bool CodeCompletion::triggersCompletion(TextEditor::ITextEditable *editor)
QTextCursor tc = ed->textCursor(); QTextCursor tc = ed->textCursor();
QTextBlock block = tc.block(); QTextBlock block = tc.block();
const int column = tc.positionInBlock(); const int column = tc.positionInBlock();
const QChar ch = block.text().at(column - 1);
const int blockState = qMax(0, block.previous().userState()) & 0xff; const int blockState = qMax(0, block.previous().userState()) & 0xff;
const QString blockText = block.text(); const QString blockText = block.text();
@@ -527,10 +530,11 @@ bool CodeCompletion::triggersCompletion(TextEditor::ITextEditable *editor)
const QList<Token> tokens = scanner(blockText, blockState); const QList<Token> tokens = scanner(blockText, blockState);
foreach (const Token &tk, tokens) { foreach (const Token &tk, tokens) {
if (column >= tk.begin() && column <= tk.end()) { if (column >= tk.begin() && column <= tk.end()) {
if (ch == QLatin1Char('/') && tk.is(Token::String))
return true; // path completion inside string literals
if (tk.is(Token::Comment) || tk.is(Token::String)) if (tk.is(Token::Comment) || tk.is(Token::String))
return false; return false;
else break;
break;
} }
} }
} }
@@ -545,7 +549,7 @@ bool CodeCompletion::maybeTriggersCompletion(TextEditor::ITextEditable *editor)
const int cursorPosition = editor->position(); const int cursorPosition = editor->position();
const QChar ch = editor->characterAt(cursorPosition - 1); const QChar ch = editor->characterAt(cursorPosition - 1);
if (ch == QLatin1Char('(') || ch == QLatin1Char('.')) if (ch == QLatin1Char('(') || ch == QLatin1Char('.') || ch == QLatin1Char('/'))
return true; return true;
if (completionSettings().m_completionTrigger != TextEditor::AutomaticCompletion) if (completionSettings().m_completionTrigger != TextEditor::AutomaticCompletion)
return false; return false;
@@ -609,6 +613,42 @@ static bool isLiteral(AST::Node *ast)
return false; return false;
} }
bool CodeCompletion::completeUrl(const QString &relativeBasePath, const QString &urlString)
{
const QUrl url(urlString);
QString fileName = url.toLocalFile();
if (fileName.isEmpty())
return false;
const QFileInfo fileInfo(fileName);
QString directoryPrefix;
if (fileInfo.isRelative()) {
directoryPrefix = relativeBasePath;
directoryPrefix += QDir::separator();
directoryPrefix += fileInfo.path();
} else {
directoryPrefix = fileInfo.path();
}
if (!QFileInfo(directoryPrefix).exists())
return false;
QDirIterator dirIterator(directoryPrefix);
while (dirIterator.hasNext()) {
dirIterator.next();
const QString fileName = dirIterator.fileName();
if (fileName == QLatin1String(".") || fileName == QLatin1String(".."))
continue;
TextEditor::CompletionItem item(this);
item.text += fileName;
// ### Icon for file completions
item.icon = iconForColor(Qt::darkBlue);
m_completions.append(item);
}
return !m_completions.isEmpty();
}
void CodeCompletion::addCompletions(const QHash<QString, const Interpreter::Value *> &newCompletions, void CodeCompletion::addCompletions(const QHash<QString, const Interpreter::Value *> &newCompletions,
const QIcon &icon, int order) const QIcon &icon, int order)
{ {
@@ -662,6 +702,27 @@ void CodeCompletion::addCompletionsPropertyLhs(
} }
} }
static const Interpreter::Value *getPropertyValue(
const Interpreter::ObjectValue *object,
const QStringList &propertyNames,
const Interpreter::Context *context)
{
if (propertyNames.isEmpty())
return 0;
const Interpreter::Value *value = object;
foreach (const QString &name, propertyNames) {
if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) {
value = objectValue->property(name, context);
if (!value)
return 0;
} else {
return 0;
}
}
return value;
}
int CodeCompletion::startCompletion(TextEditor::ITextEditable *editor) int CodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
{ {
m_restartCompletion = false; m_restartCompletion = false;
@@ -713,8 +774,29 @@ int CodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
if (contextFinder.isInQmlContext()) if (contextFinder.isInQmlContext())
qmlScopeType = context->lookupType(document.data(), contextFinder.qmlObjectTypeName()); qmlScopeType = context->lookupType(document.data(), contextFinder.qmlObjectTypeName());
if (completionOperator.isSpace() || completionOperator.isNull() || isDelimiter(completionOperator) || if (contextFinder.isInStringLiteral()) {
(completionOperator == QLatin1Char('(') && m_startPosition != editor->position())) { const Interpreter::Value *value = getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context);
if (value->asUrlValue()) {
QTextCursor tc = edit->textCursor();
QmlExpressionUnderCursor expressionUnderCursor;
expressionUnderCursor(tc);
QString text = expressionUnderCursor.text();
QTC_ASSERT(!text.isEmpty() && text.at(0) == QLatin1Char('"'), return -1);
text = text.mid(1);
if (completeUrl(document->path(), text))
return m_startPosition;
}
// ### enum completion?
// completion gets triggered for / in string literals, if we don't
// return here, this will mean the snippet completion pops up for
// each / in a string literal that is not triggering file completion
return -1;
} else if (completionOperator.isSpace() || completionOperator.isNull() || isDelimiter(completionOperator) ||
(completionOperator == QLatin1Char('(') && m_startPosition != editor->position())) {
bool doGlobalCompletion = true; bool doGlobalCompletion = true;
bool doQmlKeywordCompletion = true; bool doQmlKeywordCompletion = true;
@@ -748,28 +830,16 @@ int CodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
if (contextFinder.isInRhsOfBinding() && qmlScopeType) { if (contextFinder.isInRhsOfBinding() && qmlScopeType) {
doQmlKeywordCompletion = false; doQmlKeywordCompletion = false;
if (!contextFinder.bindingPropertyName().isEmpty()) { // complete enum values for enum properties
const Interpreter::Value *value = qmlScopeType; const Interpreter::Value *value = getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context);
foreach (const QString &name, contextFinder.bindingPropertyName()) { if (const Interpreter::QmlEnumValue *enumValue = dynamic_cast<const Interpreter::QmlEnumValue *>(value)) {
if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) { foreach (const QString &key, enumValue->keys()) {
value = objectValue->property(name, context); TextEditor::CompletionItem item(this);
if (!value) item.text = key;
break; item.data = QString("\"%1\"").arg(key);
} else { item.icon = symbolIcon;
value = 0; item.order = EnumValueOrder;
break; m_completions.append(item);
}
}
if (const Interpreter::QmlEnumValue *enumValue = dynamic_cast<const Interpreter::QmlEnumValue *>(value)) {
foreach (const QString &key, enumValue->keys()) {
TextEditor::CompletionItem item(this);
item.text = key;
item.data = QString("\"%1\"").arg(key);
item.icon = symbolIcon;
item.order = EnumValueOrder;
m_completions.append(item);
}
} }
} }
} }

View File

@@ -81,6 +81,8 @@ private:
bool maybeTriggersCompletion(TextEditor::ITextEditable *editor); bool maybeTriggersCompletion(TextEditor::ITextEditable *editor);
bool isDelimiter(QChar ch) const; bool isDelimiter(QChar ch) const;
bool completeUrl(const QString &relativeBasePath, const QString &name);
void addCompletions(const QHash<QString, const QmlJS::Interpreter::Value *> &newCompletions, void addCompletions(const QHash<QString, const QmlJS::Interpreter::Value *> &newCompletions,
const QIcon &icon, int relevance); const QIcon &icon, int relevance);
void addCompletions(const QStringList &newCompletions, void addCompletions(const QStringList &newCompletions,