diff --git a/src/plugins/cppeditor/cppplugin.cpp b/src/plugins/cppeditor/cppplugin.cpp index 6f829843d85d917cfbb131b2b82581977d811e76..35453109ba55a064867b1b56cc3f9301f6ba5964 100644 --- a/src/plugins/cppeditor/cppplugin.cpp +++ b/src/plugins/cppeditor/cppplugin.cpp @@ -134,6 +134,11 @@ void CppPlugin::initializeEditor(CPPEditor *editor) // auto completion connect(editor, SIGNAL(requestAutoCompletion(ITextEditable*, bool)), TextEditor::Internal::CompletionSupport::instance(), SLOT(autoComplete(ITextEditable*, bool))); + + // quick fix + connect(editor, SIGNAL(requestQuickFix(ITextEditable*)), + TextEditor::Internal::CompletionSupport::instance(), SLOT(quickFix(ITextEditable*))); + // method combo box sorting connect(this, SIGNAL(methodOverviewSortingChanged(bool)), editor, SLOT(setSortedMethodOverview(bool))); diff --git a/src/plugins/cpptools/cppcodecompletion.cpp b/src/plugins/cpptools/cppcodecompletion.cpp index d69d683f819a6fb39b0416d0ed6a2e6974f98988..ffa09ae1c89c010b1417921666a7fee45e419a24 100644 --- a/src/plugins/cpptools/cppcodecompletion.cpp +++ b/src/plugins/cpptools/cppcodecompletion.cpp @@ -30,6 +30,7 @@ #include "cppcodecompletion.h" #include "cppmodelmanager.h" #include "cppdoxygen.h" +#include "cpptoolseditorsupport.h" #include <Control.h> #include <AST.h> @@ -421,6 +422,59 @@ void FunctionArgumentWidget::updateHintText() m_popupFrame->move(pos); } +CppQuickFixCollector::CppQuickFixCollector(CppModelManager *modelManager) + : _modelManager(modelManager), _editor(0) +{ } + +CppQuickFixCollector::~CppQuickFixCollector() +{ } + +bool CppQuickFixCollector::supportsEditor(TextEditor::ITextEditable *editor) +{ return _modelManager->isCppEditor(editor); } + +bool CppQuickFixCollector::triggersCompletion(TextEditor::ITextEditable *) +{ return false; } + +int CppQuickFixCollector::startCompletion(TextEditor::ITextEditable *editor) +{ + _editor = editor; + + if (CppEditorSupport *extra = _modelManager->editorSupport(editor)) { + const QList<QuickFixOperationPtr> quickFixes = extra->quickFixes(); + if (! quickFixes.isEmpty()) { + int i = 0; + foreach (QuickFixOperationPtr op, quickFixes) { + TextEditor::CompletionItem item(this); + item.m_text = op->description(); + item.m_data = QVariant::fromValue(i); + _completions.append(item); + ++i; + } + return editor->position(); + } + } + return -1; +} + +void CppQuickFixCollector::completions(QList<TextEditor::CompletionItem> *completions) +{ + completions->append(_completions); +} + +void CppQuickFixCollector::complete(const TextEditor::CompletionItem &item) +{ + CppEditorSupport *extra = _modelManager->editorSupport(_editor); + const QList<QuickFixOperationPtr> quickFixes = extra->quickFixes(); + QuickFixOperationPtr quickFix = quickFixes.at(item.m_data.toInt()); + TextEditor::BaseTextEditor *ed = qobject_cast<TextEditor::BaseTextEditor *>(_editor->widget()); + quickFix->apply(ed->textCursor()); +} + +void CppQuickFixCollector::cleanup() +{ + _completions.clear(); +} + CppCodeCompletion::CppCodeCompletion(CppModelManager *manager) : ICompletionCollector(manager), m_manager(manager), diff --git a/src/plugins/cpptools/cppcodecompletion.h b/src/plugins/cpptools/cppcodecompletion.h index d32b81703f4e1f85dbb33ed2e4e1f8b2f6a36506..7a00f364b12d538b06ff42bc6247fb175076ad07 100644 --- a/src/plugins/cpptools/cppcodecompletion.h +++ b/src/plugins/cpptools/cppcodecompletion.h @@ -52,6 +52,27 @@ namespace Internal { class CppModelManager; class FunctionArgumentWidget; +class CppQuickFixCollector: public TextEditor::IQuickFixCollector +{ + Q_OBJECT + +public: + CppQuickFixCollector(CppModelManager *modelManager); + virtual ~CppQuickFixCollector(); + + 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 void cleanup(); + +private: + CppModelManager *_modelManager; + TextEditor::ITextEditable *_editor; + QList<TextEditor::CompletionItem> _completions; +}; + class CppCodeCompletion : public TextEditor::ICompletionCollector { Q_OBJECT diff --git a/src/plugins/cpptools/cppmodelmanager.h b/src/plugins/cpptools/cppmodelmanager.h index d768d8d50b30a0c21deef699bda60d64a9608a84..5069f039a82014cc8eebeae24028aab0bf763bfb 100644 --- a/src/plugins/cpptools/cppmodelmanager.h +++ b/src/plugins/cpptools/cppmodelmanager.h @@ -86,6 +86,9 @@ public: bool isCppEditor(Core::IEditor *editor) const; // ### private + CppEditorSupport *editorSupport(TextEditor::ITextEditor *editor) const + { return m_editorSupport.value(editor); } + void emitDocumentUpdated(CPlusPlus::Document::Ptr doc); void stopEditorSelectionsUpdate() diff --git a/src/plugins/cpptools/cpptoolseditorsupport.cpp b/src/plugins/cpptools/cpptoolseditorsupport.cpp index a73e91b8dada8ef75a9260bea4a3774595ee215f..1d69f3f7eb359c8dabbc65b075a825edfccc1427 100644 --- a/src/plugins/cpptools/cpptoolseditorsupport.cpp +++ b/src/plugins/cpptools/cpptoolseditorsupport.cpp @@ -33,9 +33,214 @@ #include <texteditor/itexteditor.h> #include <texteditor/basetexteditor.h> -#include <QTimer> +#include <AST.h> +#include <ASTVisitor.h> +#include <TranslationUnit.h> + +#include <QtCore/QTimer> using namespace CppTools::Internal; +using namespace CPlusPlus; + +namespace { + +enum { + DEFAULT_QUICKFIX_INTERVAL = 500 +}; + +class QuickFixMark: public TextEditor::ITextMark +{ + QIcon _icon; + +public: + QuickFixMark(QObject *parent) + : TextEditor::ITextMark(parent), + _icon(QLatin1String(":/core/images/redo.png")) // ### FIXME + { } + + virtual ~QuickFixMark() + { } + + virtual QIcon icon() const + { return _icon; } + + virtual void updateLineNumber(int) + { } + + virtual void updateBlock(const QTextBlock &) + { } + + virtual void removedFromEditor() + { } + + virtual void documentClosing() + { } +}; + +class ReplaceCast: public QuickFixOperation +{ + CastExpressionAST *_castExpression; + +public: + ReplaceCast(CastExpressionAST *node, Document::Ptr doc, const Snapshot &snapshot) + : QuickFixOperation(doc, snapshot), + _castExpression(node) + { } + + virtual QString description() const + { return QLatin1String("Rewrite old C-style cast"); } + + virtual void apply(QTextCursor tc) + { + setTextCursor(tc); + + tc.beginEditBlock(); + + QTextCursor beginOfCast = cursor(_castExpression->lparen_token); + QTextCursor endOfCast = cursor(_castExpression->rparen_token); + QTextCursor beginOfExpr = moveAtStartOfToken(_castExpression->expression->firstToken()); + QTextCursor endOfExpr = moveAtEndOfToken(_castExpression->expression->lastToken() - 1); + + beginOfCast.insertText(QLatin1String("reinterpret_cast<")); + endOfCast.insertText(QLatin1String(">")); + + beginOfExpr.insertText(QLatin1String("(")); + endOfExpr.insertText(QLatin1String(")")); + + tc.endEditBlock(); + } +}; + +class CheckDocument: protected ASTVisitor +{ + QTextCursor _textCursor; + Document::Ptr _doc; + Snapshot _snapshot; + unsigned _line; + unsigned _column; + QList<QuickFixOperationPtr> _quickFixes; + +public: + CheckDocument(Document::Ptr doc, Snapshot snapshot) + : ASTVisitor(doc->control()), _doc(doc), _snapshot(snapshot) + { } + + QList<QuickFixOperationPtr> operator()(QTextCursor tc) + { + _quickFixes.clear(); + _textCursor = tc; + _line = tc.blockNumber() + 1; + _column = tc.columnNumber() + 1; + accept(_doc->translationUnit()->ast()); + return _quickFixes; + } + +protected: + using ASTVisitor::visit; + + bool checkPosition(AST *ast) const + { + unsigned startLine, startColumn; + unsigned endLine, endColumn; + + getTokenStartPosition(ast->firstToken(), &startLine, &startColumn); + getTokenEndPosition(ast->lastToken() - 1, &endLine, &endColumn); + + if (_line < startLine || (_line == startLine && _column < startColumn)) + return false; + else if (_line > endLine || (_line == endLine && _column >= endColumn)) + return false; + + return true; + } + + /* + virtual bool visit(ForStatementAST *ast) + { + if (! checkPosition(ast)) + return true; + + if (ast->initializer && ast->initializer->asDeclarationStatement() != 0) { + if (checkPosition(ast->initializer)) { + // move initializer + _nodes.append(ast); + } + } + + return true; + } + */ + + + virtual bool visit(CastExpressionAST *ast) + { + if (! checkPosition(ast)) + return true; + + if (ast->type_id && ast->lparen_token && ast->rparen_token && ast->expression) { + QuickFixOperationPtr op(new ReplaceCast(ast, _doc, _snapshot)); + _quickFixes.append(op); + } + + return true; + } +}; + +} // end of anonymous namespace + +QuickFixOperation::QuickFixOperation(CPlusPlus::Document::Ptr doc, const CPlusPlus::Snapshot &snapshot) + : _doc(doc), _snapshot(snapshot) +{ } + +QuickFixOperation::~QuickFixOperation() +{ } + +QTextCursor QuickFixOperation::textCursor() const +{ return _textCursor; } + +void QuickFixOperation::setTextCursor(const QTextCursor &tc) +{ _textCursor = tc; } + +const CPlusPlus::Token &QuickFixOperation::tokenAt(unsigned index) const +{ return _doc->translationUnit()->tokenAt(index); } + +void QuickFixOperation::getTokenStartPosition(unsigned index, unsigned *line, unsigned *column) const +{ _doc->translationUnit()->getPosition(tokenAt(index).begin(), line, column); } + +void QuickFixOperation::getTokenEndPosition(unsigned index, unsigned *line, unsigned *column) const +{ _doc->translationUnit()->getPosition(tokenAt(index).end(), line, column); } + +QTextCursor QuickFixOperation::cursor(unsigned index) const +{ + const Token &tk = tokenAt(index); + + unsigned line, col; + getTokenStartPosition(index, &line, &col); + QTextCursor tc = _textCursor; + tc.setPosition(tc.document()->findBlockByNumber(line - 1).position() + col - 1); + tc.setPosition(tc.position() + tk.length, QTextCursor::KeepAnchor); + return tc; +} + +QTextCursor QuickFixOperation::moveAtStartOfToken(unsigned index) const +{ + unsigned line, col; + getTokenStartPosition(index, &line, &col); + QTextCursor tc = _textCursor; + tc.setPosition(tc.document()->findBlockByNumber(line - 1).position() + col - 1); + return tc; +} + +QTextCursor QuickFixOperation::moveAtEndOfToken(unsigned index) const +{ + const Token &tk = tokenAt(index); + + unsigned line, col; + getTokenStartPosition(index, &line, &col); + QTextCursor tc = _textCursor; + tc.setPosition(tc.document()->findBlockByNumber(line - 1).position() + col + tk.length - 1); + return tc; +} CppEditorSupport::CppEditorSupport(CppModelManager *modelManager) : QObject(modelManager), @@ -46,6 +251,13 @@ CppEditorSupport::CppEditorSupport(CppModelManager *modelManager) _updateDocumentTimer->setSingleShot(true); _updateDocumentTimer->setInterval(_updateDocumentInterval); connect(_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(updateDocumentNow())); + + _quickFixMark = new QuickFixMark(this); + + _quickFixTimer = new QTimer(this); + _quickFixTimer->setSingleShot(true); + _quickFixTimer->setInterval(DEFAULT_QUICKFIX_INTERVAL); + connect(_quickFixTimer, SIGNAL(timeout()), this, SLOT(checkDocumentNow())); } CppEditorSupport::~CppEditorSupport() @@ -62,7 +274,11 @@ void CppEditorSupport::setTextEditor(TextEditor::ITextEditor *textEditor) return; connect(_textEditor, SIGNAL(contentsChanged()), this, SIGNAL(contentsChanged())); + connect(qobject_cast<TextEditor::BaseTextEditor *>(_textEditor->widget()), SIGNAL(cursorPositionChanged()), + this, SLOT(checkDocument())); + connect(this, SIGNAL(contentsChanged()), this, SLOT(updateDocument())); + updateDocument(); } @@ -107,5 +323,35 @@ void CppEditorSupport::updateDocumentNow() } } +void CppEditorSupport::checkDocument() +{ + _quickFixTimer->start(DEFAULT_QUICKFIX_INTERVAL); +} + +void CppEditorSupport::checkDocumentNow() +{ + _textEditor->markableInterface()->removeMark(_quickFixMark); + _quickFixes.clear(); + + TextEditor::BaseTextEditor *ed = + qobject_cast<TextEditor::BaseTextEditor *>(_textEditor->widget()); + + Snapshot snapshot = _modelManager->snapshot(); + const QByteArray plainText = contents(); + const QString fileName = _textEditor->file()->fileName(); + const QByteArray preprocessedCode = snapshot.preprocessedCode(plainText, fileName); + + if (Document::Ptr doc = snapshot.documentFromSource(preprocessedCode, fileName)) { + CheckDocument checkDocument(doc, snapshot); + QList<QuickFixOperationPtr> quickFixes = checkDocument(ed->textCursor()); + if (! quickFixes.isEmpty()) { + int line, col; + ed->convertPosition(ed->position(), &line, &col); + + _textEditor->markableInterface()->addMark(_quickFixMark, line); + _quickFixes = quickFixes; + } + } +} diff --git a/src/plugins/cpptools/cpptoolseditorsupport.h b/src/plugins/cpptools/cpptoolseditorsupport.h index 4df23c7b2561e6c2de34859216d8ff02a7e4f82c..f962ca006337edec175d0cd27b786cbf11e74b82 100644 --- a/src/plugins/cpptools/cpptoolseditorsupport.h +++ b/src/plugins/cpptools/cpptoolseditorsupport.h @@ -33,13 +33,21 @@ #include <QObject> #include <QPointer> #include <QFuture> +#include <QSharedPointer> +#include <QTextCursor.h> +#include <cplusplus/CppDocument.h> QT_BEGIN_NAMESPACE class QTimer; QT_END_NAMESPACE +namespace CPlusPlus { + class AST; +} + namespace TextEditor { class ITextEditor; + class ITextMark; } // end of namespace TextEditor namespace CppTools { @@ -47,6 +55,46 @@ namespace Internal { class CppModelManager; +class QuickFixOperation; +typedef QSharedPointer<QuickFixOperation> QuickFixOperationPtr; + +class QuickFixOperation +{ + Q_DISABLE_COPY(QuickFixOperation) + +public: + QuickFixOperation(CPlusPlus::Document::Ptr doc, + const CPlusPlus::Snapshot &snapshot); + + virtual ~QuickFixOperation(); + + virtual QString description() const = 0; + virtual void apply(QTextCursor cursor) = 0; + + CPlusPlus::Document::Ptr document() const { return _doc; } + CPlusPlus::Snapshot snapshot() const { return _snapshot; } + + QTextCursor textCursor() const; + void setTextCursor(const QTextCursor &tc); + +protected: + const CPlusPlus::Token &tokenAt(unsigned index) const; + void getTokenStartPosition(unsigned index, unsigned *line, + unsigned *column) const; + void getTokenEndPosition(unsigned index, unsigned *line, + unsigned *column) const; + + QTextCursor cursor(unsigned index) const; + QTextCursor moveAtStartOfToken(unsigned index) const; + QTextCursor moveAtEndOfToken(unsigned index) const; + +private: + CPlusPlus::AST *_node; + CPlusPlus::Document::Ptr _doc; + CPlusPlus::Snapshot _snapshot; + QTextCursor _textCursor; +}; + class CppEditorSupport: public QObject { Q_OBJECT @@ -55,6 +103,9 @@ public: CppEditorSupport(CppModelManager *modelManager); virtual ~CppEditorSupport(); + QList<QuickFixOperationPtr> quickFixes() const + { return _quickFixes; } + TextEditor::ITextEditor *textEditor() const; void setTextEditor(TextEditor::ITextEditor *textEditor); @@ -70,6 +121,9 @@ private Q_SLOTS: void updateDocument(); void updateDocumentNow(); + void checkDocument(); + void checkDocumentNow(); + private: enum { UPDATE_DOCUMENT_DEFAULT_INTERVAL = 150 }; @@ -79,6 +133,10 @@ private: int _updateDocumentInterval; QFuture<void> _documentParser; QByteArray _cachedContents; + + QTimer *_quickFixTimer; + TextEditor::ITextMark *_quickFixMark; + QList<QuickFixOperationPtr> _quickFixes; }; } // namespace Internal diff --git a/src/plugins/cpptools/cpptoolsplugin.cpp b/src/plugins/cpptools/cpptoolsplugin.cpp index 470aeea146792994d723626bc9f3e8673f152c95..62d77eb27d2df62bd74a32f42477236a73edf23a 100644 --- a/src/plugins/cpptools/cpptoolsplugin.cpp +++ b/src/plugins/cpptools/cpptoolsplugin.cpp @@ -151,8 +151,12 @@ bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error) // Objects m_modelManager = new CppModelManager(this); addAutoReleasedObject(m_modelManager); + m_completion = new CppCodeCompletion(m_modelManager); addAutoReleasedObject(m_completion); + + addAutoReleasedObject(new CppQuickFixCollector(m_modelManager)); + CppQuickOpenFilter *quickOpenFilter = new CppQuickOpenFilter(m_modelManager, core->editorManager()); addAutoReleasedObject(quickOpenFilter); diff --git a/src/plugins/texteditor/completionsupport.cpp b/src/plugins/texteditor/completionsupport.cpp index 3750f6b88a8309a432a68d83f8f126ea7a4f802b..b3a3282fc440943896a4af06f9326ebf8d2ac887 100644 --- a/src/plugins/texteditor/completionsupport.cpp +++ b/src/plugins/texteditor/completionsupport.cpp @@ -96,7 +96,6 @@ void CompletionSupport::autoComplete(ITextEditable *editor, bool forced) void CompletionSupport::quickFix(ITextEditable *editor) { - qDebug() << Q_FUNC_INFO; autoComplete_helper(editor, /*forced = */ true, /*quickFix = */ true); diff --git a/src/plugins/texteditor/icompletioncollector.h b/src/plugins/texteditor/icompletioncollector.h index eb4836f40cb3003a8cff3944323c9b8faf85e553..02d8061742f7855fed8663ab3439c587e0d7d330 100644 --- a/src/plugins/texteditor/icompletioncollector.h +++ b/src/plugins/texteditor/icompletioncollector.h @@ -121,6 +121,9 @@ public: IQuickFixCollector(QObject *parent = 0) : ICompletionCollector(parent) {} virtual ~IQuickFixCollector() {} + virtual bool triggersCompletion(TextEditor::ITextEditable *) + { return false; } + virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &) { return false; } }; diff --git a/src/plugins/texteditor/texteditorplugin.cpp b/src/plugins/texteditor/texteditorplugin.cpp index 50ab05417cda3347d2dfaa72cafc40e3af78caad..ed95c0d3ea280a7463f0c2dc138d67e6cb54b3b8 100644 --- a/src/plugins/texteditor/texteditorplugin.cpp +++ b/src/plugins/texteditor/texteditorplugin.cpp @@ -137,6 +137,7 @@ bool TextEditorPlugin::initialize(const QStringList &arguments, QString *errorMe // Make sure the shortcut still works when the quick fix widget is active quickFixShortcut->setContext(Qt::ApplicationShortcut); Core::Command *quickFixCommand = am->registerShortcut(quickFixShortcut, Constants::QUICKFIX_THIS, context); + quickFixCommand->setDefaultKeySequence(QKeySequence(tr("Alt+Return"))); connect(quickFixShortcut, SIGNAL(activated()), this, SLOT(invokeQuickFix())); return true;