Commit df16c1b6 authored by Roberto Raggi's avatar Roberto Raggi

Initial work on the DUI editor-plugin.

parent ef8e69d9
<?xml version="1.0"?>
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
<mime-type type="application/x-dui">
<sub-class-of type="text/plain"/>
<comment>DUI file</comment>
<glob pattern="*.dui"/>
</mime-type>
</mime-info>
<plugin name="DuiEditor" version="1.1.80" compatVersion="1.1.80">
<vendor>Nokia Corporation</vendor>
<copyright>(C) 2008-2009 Nokia Corporation</copyright>
<license>
Commercial Usage
Licensees holding valid Qt Commercial licenses may use this plugin in
accordance with the Qt Commercial License Agreement provided with the
Software or, alternatively, in accordance with the terms contained in
a written agreement between you and Nokia.
GNU Lesser General Public License Usage
Alternatively, this plugin may be used under the terms of the GNU Lesser
General Public License version 2.1 as published by the Free Software
Foundation. 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.</license>
<description>Editor for DUI.</description>
<url>http://www.qtsoftware.com</url>
<dependencyList>
<dependency name="Core" version="1.1.80"/>
<dependency name="TextEditor" version="1.1.80"/>
</dependencyList>
</plugin>
#include "duicodecompletion.h"
#include "duieditor.h"
#include <texteditor/basetexteditor.h>
#include <QtDebug>
using namespace DuiEditor::Internal;
DuiCodeCompletion::DuiCodeCompletion(QObject *parent)
: TextEditor::ICompletionCollector(parent),
m_editor(0),
m_startPosition(0),
m_caseSensitivity(Qt::CaseSensitive)
{ }
DuiCodeCompletion::~DuiCodeCompletion()
{ }
Qt::CaseSensitivity DuiCodeCompletion::caseSensitivity() const
{ return m_caseSensitivity; }
void DuiCodeCompletion::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
{ m_caseSensitivity = caseSensitivity; }
bool DuiCodeCompletion::supportsEditor(TextEditor::ITextEditable *editor)
{
if (qobject_cast<ScriptEditor *>(editor->widget()))
return true;
return false;
}
bool DuiCodeCompletion::triggersCompletion(TextEditor::ITextEditable *)
{ return false; }
int DuiCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
{
m_editor = editor;
ScriptEditor *edit = qobject_cast<ScriptEditor *>(m_editor->widget());
if (! edit)
return -1;
int pos = editor->position();
while (editor->characterAt(pos - 1).isLetterOrNumber() || editor->characterAt(pos - 1) == QLatin1Char('_'))
--pos;
m_startPosition = pos;
m_completions.clear();
foreach (const QString &word, edit->words()) {
TextEditor::CompletionItem item(this);
item.m_text = word;
m_completions.append(item);
}
return pos;
}
void DuiCodeCompletion::completions(QList<TextEditor::CompletionItem> *completions)
{
// ### FIXME: this code needs to be generalized.
const int length = m_editor->position() - m_startPosition;
if (length == 0)
*completions = m_completions;
else if (length > 0) {
const QString key = m_editor->textAt(m_startPosition, length);
/*
* This code builds a regular expression in order to more intelligently match
* camel-case style. This means upper-case characters will be rewritten as follows:
*
* A => [a-z0-9_]*A (for any but the first capital letter)
*
* Meaning it allows any sequence of lower-case characters to preceed an
* upper-case character. So for example gAC matches getActionController.
*/
QString keyRegExp;
keyRegExp += QLatin1Char('^');
bool first = true;
foreach (const QChar &c, key) {
if (c.isUpper() && !first) {
keyRegExp += QLatin1String("[a-z0-9_]*");
keyRegExp += c;
} else if (m_caseSensitivity == Qt::CaseInsensitive && c.isLower()) {
keyRegExp += QLatin1Char('[');
keyRegExp += c;
keyRegExp += c.toUpper();
keyRegExp += QLatin1Char(']');
} else {
keyRegExp += QRegExp::escape(c);
}
first = false;
}
const QRegExp regExp(keyRegExp, Qt::CaseSensitive);
foreach (TextEditor::CompletionItem item, m_completions) {
if (regExp.indexIn(item.m_text) == 0) {
item.m_relevance = (key.length() > 0 &&
item.m_text.startsWith(key, Qt::CaseInsensitive)) ? 1 : 0;
(*completions) << item;
}
}
}
}
void DuiCodeCompletion::complete(const TextEditor::CompletionItem &item)
{
const QString toInsert = item.m_text;
const int length = m_editor->position() - m_startPosition;
m_editor->setCurPos(m_startPosition);
m_editor->replace(length, toInsert);
}
bool DuiCodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems)
{
if (completionItems.count() == 1) {
complete(completionItems.first());
return true;
} else {
// Compute common prefix
QString firstKey = completionItems.first().m_text;
QString lastKey = completionItems.last().m_text;
const int length = qMin(firstKey.length(), lastKey.length());
firstKey.truncate(length);
lastKey.truncate(length);
while (firstKey != lastKey) {
firstKey.chop(1);
lastKey.chop(1);
}
int typedLength = m_editor->position() - m_startPosition;
if (!firstKey.isEmpty() && firstKey.length() > typedLength) {
m_editor->setCurPos(m_startPosition);
m_editor->replace(typedLength, firstKey);
}
}
return false;
}
void DuiCodeCompletion::cleanup()
{
m_editor = 0;
m_startPosition = 0;
m_completions.clear();
}
#ifndef DUICODECOMPLETION_H
#define DUICODECOMPLETION_H
#include <texteditor/icompletioncollector.h>
namespace TextEditor {
class ITextEditable;
}
namespace DuiEditor {
namespace Internal {
class DuiCodeCompletion: public TextEditor::ICompletionCollector
{
Q_OBJECT
public:
DuiCodeCompletion(QObject *parent = 0);
virtual ~DuiCodeCompletion();
Qt::CaseSensitivity caseSensitivity() const;
void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity);
virtual bool supportsEditor(TextEditor::ITextEditable *editor);
virtual bool triggersCompletion(TextEditor::ITextEditable *editor);
virtual int startCompletion(TextEditor::ITextEditable *editor);
virtual void completions(QList<TextEditor::CompletionItem> *completions);
virtual void complete(const TextEditor::CompletionItem &item);
virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems);
virtual void cleanup();
private:
TextEditor::ITextEditable *m_editor;
int m_startPosition;
QList<TextEditor::CompletionItem> m_completions;
Qt::CaseSensitivity m_caseSensitivity;
};
} // end of namespace Internal
} // end of namespace DuiEditor
#endif // DUICODECOMPLETION_H
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
**
**************************************************************************/
#include "duieditor.h"
#include "duieditorconstants.h"
#include "duihighlighter.h"
#include "duieditorplugin.h"
#include "parser/javascriptengine_p.h"
#include "parser/javascriptparser_p.h"
#include "parser/javascriptlexer_p.h"
#include "parser/javascriptnodepool_p.h"
#include "parser/javascriptastvisitor_p.h"
#include "parser/javascriptast_p.h"
#include <indenter.h>
#include <coreplugin/icore.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <texteditor/basetextdocument.h>
#include <texteditor/fontsettings.h>
#include <texteditor/textblockiterator.h>
#include <texteditor/texteditorconstants.h>
#include <texteditor/texteditorsettings.h>
#include <QtCore/QTimer>
#include <QtCore/QtDebug>
#include <QtGui/QMenu>
#include <QtGui/QComboBox>
enum {
UPDATE_DOCUMENT_DEFAULT_INTERVAL = 250
};
using namespace JavaScript::AST;
namespace DuiEditor {
namespace Internal {
class FindDeclarations: protected Visitor
{
QList<Declaration> declarations;
public:
QList<Declaration> accept(JavaScript::AST::Node *node)
{
JavaScript::AST::Node::acceptChild(node, this);
return declarations;
}
protected:
using Visitor::visit;
virtual bool visit(FunctionExpression *)
{
return false;
}
virtual bool visit(FunctionDeclaration *ast)
{
if (! ast->name)
return false;
QString text = ast->name->asString();
text += QLatin1Char('(');
for (FormalParameterList *it = ast->formals; it; it = it->next) {
if (it->name)
text += it->name->asString();
if (it->next)
text += QLatin1String(", ");
}
text += QLatin1Char(')');
Declaration d;
d.text = text;
#if 0 // ### FIXME
d.startLine = ast->startLine;
d.startColumn = ast->startColumn;
d.endLine = ast->endLine;
d.endColumn = ast->endColumn;
#endif
declarations.append(d);
return false;
}
virtual bool visit(VariableDeclaration *ast)
{
if (! ast->name)
return false;
Declaration d;
d.text = ast->name->asString();
#if 0 // ### FIXME
d.startLine= ast->startLine;
d.startColumn = ast->startColumn;
d.endLine = ast->endLine;
d.endColumn = ast->endColumn;
#endif
declarations.append(d);
return false;
}
};
ScriptEditorEditable::ScriptEditorEditable(ScriptEditor *editor, const QList<int>& context)
: BaseTextEditorEditable(editor), m_context(context)
{
}
ScriptEditor::ScriptEditor(const Context &context,
QWidget *parent) :
TextEditor::BaseTextEditor(parent),
m_context(context),
m_methodCombo(0)
{
setParenthesesMatchingEnabled(true);
setMarksVisible(true);
setCodeFoldingSupported(true);
setCodeFoldingVisible(true);
setMimeType(DuiEditor::Constants::C_DUIEDITOR_MIMETYPE);
m_updateDocumentTimer = new QTimer(this);
m_updateDocumentTimer->setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
m_updateDocumentTimer->setSingleShot(true);
connect(m_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(updateDocumentNow()));
connect(this, SIGNAL(textChanged()), this, SLOT(updateDocument()));
baseTextDocument()->setSyntaxHighlighter(new DuiHighlighter);
}
ScriptEditor::~ScriptEditor()
{
}
QList<Declaration> ScriptEditor::declarations() const
{ return m_declarations; }
QStringList ScriptEditor::words() const
{ return m_words; }
Core::IEditor *ScriptEditorEditable::duplicate(QWidget *parent)
{
ScriptEditor *newEditor = new ScriptEditor(m_context, parent);
newEditor->duplicateFrom(editor());
DuiEditorPlugin::instance()->initializeEditor(newEditor);
return newEditor->editableInterface();
}
const char *ScriptEditorEditable::kind() const
{
return DuiEditor::Constants::C_DUIEDITOR;
}
ScriptEditor::Context ScriptEditorEditable::context() const
{
return m_context;
}
void ScriptEditor::updateDocument()
{
m_updateDocumentTimer->start(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
}
void ScriptEditor::updateDocumentNow()
{
// ### move in the parser thread.
m_updateDocumentTimer->stop();
const QString fileName = file()->fileName();
const QString code = toPlainText();
JavaScriptParser parser;
JavaScriptEnginePrivate driver;
JavaScript::NodePool nodePool(fileName, &driver);
driver.setNodePool(&nodePool);
JavaScript::Lexer lexer(&driver);
lexer.setCode(code, /*line = */ 1);
driver.setLexer(&lexer);
if (parser.parse(&driver)) {
FindDeclarations decls;
m_declarations = decls.accept(driver.ast());
m_words.clear();
foreach (const JavaScriptNameIdImpl &id, driver.literals())
m_words.append(id.asString());
QStringList items;
items.append(tr("<Select Symbol>"));
foreach (Declaration decl, m_declarations)
items.append(decl.text);
m_methodCombo->clear();
m_methodCombo->addItems(items);
updateMethodBoxIndex();
}
QList<QTextEdit::ExtraSelection> selections;
QTextCharFormat errorFormat;
errorFormat.setUnderlineColor(Qt::red);
errorFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline);
QTextEdit::ExtraSelection sel;
foreach (const JavaScriptParser::DiagnosticMessage &d, parser.diagnosticMessages()) {
if (d.isWarning())
continue;
int line = d.line;
int column = d.column;
if (column == 0)
column = 1;
QTextCursor c(document()->findBlockByNumber(line - 1));
sel.cursor = c;
sel.cursor.setPosition(c.position() + column - 1);
sel.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
sel.format = errorFormat;
selections.append(sel);
}
setExtraSelections(CodeWarningsSelection, selections);
}
void ScriptEditor::jumpToMethod(int index)
{
if (index) {
Declaration d = m_declarations.at(index - 1);
gotoLine(d.startLine, d.startColumn - 1);
setFocus();
}
}
void ScriptEditor::updateMethodBoxIndex()
{
int line = 0, column = 0;
convertPosition(position(), &line, &column);
int currentSymbolIndex = 0;
int index = 0;
while (index < m_declarations.size()) {
const Declaration &d = m_declarations.at(index++);
if (line < d.startLine)
break;
else
currentSymbolIndex = index;
}
m_methodCombo->setCurrentIndex(currentSymbolIndex);
}
void ScriptEditor::updateMethodBoxToolTip()
{
}
void ScriptEditor::updateFileName()
{
}
void ScriptEditor::setFontSettings(const TextEditor::FontSettings &fs)
{
TextEditor::BaseTextEditor::setFontSettings(fs);
DuiHighlighter *highlighter = qobject_cast<DuiHighlighter*>(baseTextDocument()->syntaxHighlighter());
if (!highlighter)
return;
static QVector<QString> categories;
if (categories.isEmpty()) {
categories << QLatin1String(TextEditor::Constants::C_NUMBER)
<< QLatin1String(TextEditor::Constants::C_STRING)
<< QLatin1String(TextEditor::Constants::C_TYPE)
<< QLatin1String(TextEditor::Constants::C_KEYWORD)
<< QLatin1String(TextEditor::Constants::C_PREPROCESSOR)
<< QLatin1String(TextEditor::Constants::C_LABEL)
<< QLatin1String(TextEditor::Constants::C_COMMENT);
}
highlighter->setFormats(fs.toTextCharFormats(categories));
highlighter->rehighlight();
}
bool ScriptEditor::isElectricCharacter(const QChar &ch) const
{
if (ch == QLatin1Char('{') || ch == QLatin1Char('}'))
return true;
return false;
}
// Indent a code line based on previous
template <class Iterator>
static void indentScriptBlock(const TextEditor::TabSettings &ts,
const QTextBlock &block,
const Iterator &programBegin,
const Iterator &programEnd,
QChar typedChar)
{
typedef typename SharedTools::Indenter<Iterator> Indenter ;
Indenter &indenter = Indenter::instance();
indenter.setTabSize(ts.m_tabSize);
indenter.setIndentSize(ts.m_indentSize);
const TextEditor::TextBlockIterator current(block);
const int indent = indenter.indentForBottomLine(current, programBegin,
programEnd, typedChar);
ts.indentLine(block, indent);
}
void ScriptEditor::indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar)
{
#if 0
const TextEditor::TextBlockIterator begin(doc->begin());
const TextEditor::TextBlockIterator end(block.next());
indentScriptBlock(tabSettings(), block, begin, end, typedChar);
#else
Q_UNUSED(doc)
Q_UNUSED(typedChar)
TextEditor::TabSettings ts = tabSettings();
const int tabSize = qMax(1, ts.m_tabSize);
int indent = 0;
QTextBlock it = block.previous();
for (; it.isValid(); it = it.previous()) {
const QString blockText = it.text();
if (! blockText.isEmpty()) {
for (int i = 0; i < blockText.length(); ++i) {
const QChar ch = blockText.at(i);
if (ch == QLatin1Char('\t'))
<