diff --git a/src/plugins/qtscripteditor/parser/javascriptengine_p.h b/src/plugins/qtscripteditor/parser/javascriptengine_p.h index 7bcbf1f3cc6a0a1017331554ca25a55e1bd58a31..480ef042a70595818773e7fdc271645b3cdcc592 100644 --- a/src/plugins/qtscripteditor/parser/javascriptengine_p.h +++ b/src/plugins/qtscripteditor/parser/javascriptengine_p.h @@ -77,6 +77,9 @@ public: : _lexer(0), _nodePool(0), _ast(0) { } + QSet<JavaScriptNameIdImpl> literals() const + { return _literals; } + JavaScriptNameIdImpl *intern(const QChar *u, int s) { return const_cast<JavaScriptNameIdImpl *>(&*_literals.insert(JavaScriptNameIdImpl(u, s))); } diff --git a/src/plugins/qtscripteditor/qtscriptcodecompletion.cpp b/src/plugins/qtscripteditor/qtscriptcodecompletion.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d08a3e175ef0a7d15d489fcb86f52d5ba505d5b9 --- /dev/null +++ b/src/plugins/qtscripteditor/qtscriptcodecompletion.cpp @@ -0,0 +1,151 @@ + +#include "qtscriptcodecompletion.h" +#include "qtscripteditor.h" +#include <texteditor/basetexteditor.h> +#include <QtDebug> + +using namespace QtScriptEditor::Internal; + +QtScriptCodeCompletion::QtScriptCodeCompletion(QObject *parent) + : TextEditor::ICompletionCollector(parent), + m_editor(0), + m_startPosition(0), + m_caseSensitivity(Qt::CaseSensitive) +{ } + +QtScriptCodeCompletion::~QtScriptCodeCompletion() +{ } + +Qt::CaseSensitivity QtScriptCodeCompletion::caseSensitivity() const +{ return m_caseSensitivity; } + +void QtScriptCodeCompletion::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity) +{ m_caseSensitivity = caseSensitivity; } + +bool QtScriptCodeCompletion::isValid(TextEditor::ITextEditable *editor) +{ + if (qobject_cast<ScriptEditor *>(editor->widget())) + return true; + + return false; +} + +bool QtScriptCodeCompletion::triggersCompletion(TextEditor::ITextEditable *) +{ return false; } + +int QtScriptCodeCompletion::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 QtScriptCodeCompletion::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 QtScriptCodeCompletion::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 QtScriptCodeCompletion::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 QtScriptCodeCompletion::cleanup() +{ + m_editor = 0; + m_startPosition = 0; + m_completions.clear(); +} + diff --git a/src/plugins/qtscripteditor/qtscriptcodecompletion.h b/src/plugins/qtscripteditor/qtscriptcodecompletion.h new file mode 100644 index 0000000000000000000000000000000000000000..a0650d5266ae07d80427e068aa2d000f31d6cbd4 --- /dev/null +++ b/src/plugins/qtscripteditor/qtscriptcodecompletion.h @@ -0,0 +1,43 @@ +#ifndef QTSCRIPTCODECOMPLETION_H +#define QTSCRIPTCODECOMPLETION_H + +#include <texteditor/icompletioncollector.h> + +namespace TextEditor { +class ITextEditable; +} + +namespace QtScriptEditor { +namespace Internal { + +class QtScriptCodeCompletion: public TextEditor::ICompletionCollector +{ + Q_OBJECT + +public: + QtScriptCodeCompletion(QObject *parent = 0); + virtual ~QtScriptCodeCompletion(); + + Qt::CaseSensitivity caseSensitivity() const; + void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity); + + virtual bool isValid(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 QtScriptEditor + +#endif // QTSCRIPTCODECOMPLETION_H diff --git a/src/plugins/qtscripteditor/qtscripteditor.cpp b/src/plugins/qtscripteditor/qtscripteditor.cpp index 916567797ed99ce1da9046d560d103eb6caf40d0..14e77fb40edd9a6208ffec3f3377fb817e713b6e 100644 --- a/src/plugins/qtscripteditor/qtscripteditor.cpp +++ b/src/plugins/qtscripteditor/qtscripteditor.cpp @@ -166,6 +166,9 @@ 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); @@ -215,6 +218,10 @@ void ScriptEditor::updateDocumentNow() 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>")); diff --git a/src/plugins/qtscripteditor/qtscripteditor.h b/src/plugins/qtscripteditor/qtscripteditor.h index dc6aa364b1178abedef299695a670c07c4da64b9..8042185796b71e59de102e1fd8d2e50c7873623c 100644 --- a/src/plugins/qtscripteditor/qtscripteditor.h +++ b/src/plugins/qtscripteditor/qtscripteditor.h @@ -91,6 +91,7 @@ public: ~ScriptEditor(); QList<Declaration> declarations() const; + QStringList words() const; public slots: virtual void setFontSettings(const TextEditor::FontSettings &); @@ -117,6 +118,7 @@ private: QTimer *m_updateDocumentTimer; QComboBox *m_methodCombo; QList<Declaration> m_declarations; + QStringList m_words; }; } // namespace Internal diff --git a/src/plugins/qtscripteditor/qtscripteditor.pro b/src/plugins/qtscripteditor/qtscripteditor.pro index 125b97e26c90d42798a5d20ba3631163c133c643..357e52df93d0e71b3799e832c510362259499a71 100644 --- a/src/plugins/qtscripteditor/qtscripteditor.pro +++ b/src/plugins/qtscripteditor/qtscripteditor.pro @@ -13,12 +13,14 @@ HEADERS += qtscripteditor.h \ qtscripteditorfactory.h \ qtscripteditorplugin.h \ qtscripthighlighter.h \ -qtscripteditoractionhandler.h +qtscripteditoractionhandler.h \ +qtscriptcodecompletion.h SOURCES += qtscripteditor.cpp \ qtscripteditorfactory.cpp \ qtscripteditorplugin.cpp \ qtscripthighlighter.cpp \ -qtscripteditoractionhandler.cpp +qtscripteditoractionhandler.cpp \ +qtscriptcodecompletion.cpp RESOURCES += qtscripteditor.qrc diff --git a/src/plugins/qtscripteditor/qtscripteditorplugin.cpp b/src/plugins/qtscripteditor/qtscripteditorplugin.cpp index b13ed76b8984ca92aa6cf7f6d1de4b99500c5ad2..eadf2711ea8c691abc5138d79318e1e223c30b96 100644 --- a/src/plugins/qtscripteditor/qtscripteditorplugin.cpp +++ b/src/plugins/qtscripteditor/qtscripteditorplugin.cpp @@ -33,6 +33,7 @@ #include "qtscripteditor.h" #include "qtscripteditorconstants.h" #include "qtscripteditorfactory.h" +#include "qtscriptcodecompletion.h" #include <coreplugin/icore.h> #include <coreplugin/coreconstants.h> @@ -46,10 +47,12 @@ #include <texteditor/texteditorsettings.h> #include <texteditor/textfilewizard.h> #include <texteditor/texteditoractionhandler.h> +#include <texteditor/completionsupport.h> #include <utils/qtcassert.h> #include <QtCore/QtPlugin> #include <QtCore/QDebug> +#include <QtCore/QSettings> #include <QtGui/QAction> using namespace QtScriptEditor::Internal; @@ -60,7 +63,8 @@ QtScriptEditorPlugin *QtScriptEditorPlugin::m_instance = 0; QtScriptEditorPlugin::QtScriptEditorPlugin() : m_wizard(0), m_editor(0), - m_actionHandler(0) + m_actionHandler(0), + m_completion(0) { m_instance = this; } @@ -105,6 +109,18 @@ bool QtScriptEditorPlugin::initialize(const QStringList & /*arguments*/, QString | TextEditor::TextEditorActionHandler::UnCommentSelection | TextEditor::TextEditorActionHandler::UnCollapseAll); + m_completion = new QtScriptCodeCompletion(); + addAutoReleasedObject(m_completion); + + // Restore settings + QSettings *settings = Core::ICore::instance()->settings(); + settings->beginGroup(QLatin1String("CppTools")); // ### FIXME: + settings->beginGroup(QLatin1String("Completion")); + const bool caseSensitive = settings->value(QLatin1String("CaseSensitive"), true).toBool(); + m_completion->setCaseSensitivity(caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); + settings->endGroup(); + settings->endGroup(); + error_message->clear(); return true; @@ -122,6 +138,10 @@ void QtScriptEditorPlugin::initializeEditor(QtScriptEditor::Internal::ScriptEdit m_actionHandler->setupActions(editor); TextEditor::TextEditorSettings::instance()->initializeEditor(editor); + + // auto completion + connect(editor, SIGNAL(requestAutoCompletion(ITextEditable*, bool)), + TextEditor::Internal::CompletionSupport::instance(), SLOT(autoComplete(ITextEditable*, bool))); } void QtScriptEditorPlugin::registerActions() diff --git a/src/plugins/qtscripteditor/qtscripteditorplugin.h b/src/plugins/qtscripteditor/qtscripteditorplugin.h index e43bb21c8a264807ca0d19161a788636ec6027be..4c1debb9fa11d7b36c9da4763331b2e9443607da 100644 --- a/src/plugins/qtscripteditor/qtscripteditorplugin.h +++ b/src/plugins/qtscripteditor/qtscripteditorplugin.h @@ -41,6 +41,7 @@ namespace QtScriptEditor { namespace Internal { class QtScriptEditorFactory; +class QtScriptCodeCompletion; class ScriptEditor; class QtScriptEditorPlugin : public ExtensionSystem::IPlugin @@ -72,6 +73,7 @@ private: TextEditor::TextFileWizard *m_wizard; QtScriptEditorFactory *m_editor; TextEditor::TextEditorActionHandler *m_actionHandler; + QtScriptCodeCompletion *m_completion; }; } // namespace Internal