Commit 88f2e340 authored by Christian Kamm's avatar Christian Kamm
Browse files

C++: Introduce the new CodeFormatter.

Done-with: Erik Verbruggen
parent 837e7b5a
This diff is collapsed.
#ifndef CPPCODEFORMATTER_H
#define CPPCODEFORMATTER_H
#include "cpptools_global.h"
#include <cplusplus/SimpleLexer.h>
#include <Token.h>
#include <QtCore/QChar>
#include <QtCore/QStack>
#include <QtCore/QList>
#include <QtCore/QVector>
#include <QtCore/QPointer>
QT_BEGIN_NAMESPACE
class QTextDocument;
class QTextBlock;
QT_END_NAMESPACE
namespace CppTools {
namespace Internal {
class CppCodeFormatterData;
}
class CPPTOOLS_EXPORT CodeFormatter
{
public:
CodeFormatter();
virtual ~CodeFormatter();
void setDocument(QTextDocument *document);
int indentFor(const QTextBlock &block);
protected:
virtual void onEnter(int newState, int *indentDepth, int *savedIndentDepth) const = 0;
virtual void adjustIndent(const QList<CPlusPlus::Token> &tokens, int lexerState, int *indentDepth) const = 0;
protected:
enum StateType {
invalid = 0,
topmost_intro, // The first line in a "topmost" definition.
multiline_comment_start, // Inside the first line of a multi-line C style block comment.
multiline_comment_cont, // Inside the following lines of a multi-line C style block comment.
cpp_macro_start, // After the '#' token
cpp_macro, // The start of a C preprocessor macro definition.
cpp_macro_cont, // Subsequent lines of a multi-line C preprocessor macro definition.
cpp_macro_conditional, // Special marker used for separating saved from current state when dealing with #ifdef
qt_like_macro, // after an identifier starting with Q_ or QT_ at the beginning of the line
defun_open, // Brace that opens a top-level function definition.
using_start, // right after the "using" token
class_start, // after the 'class' token
class_open, // Brace that opens a class definition.
member_init_open, // After ':' that starts a member initialization list.
enum_start, // After 'enum'
brace_list_open, // Open brace of an enum or static array list.
namespace_start, // after the namespace token, before the opening brace.
namespace_open, // Brace that opens a C++ namespace block.
declaration_start, // shifted a token which could start a declaration.
operator_declaration, // after 'operator' in declaration_start
template_start, // after the 'template' token
template_param, // after the '<' in a template_start
if_statement, // After 'if'
maybe_else, // after the first substatement in an if
else_clause, // The else line of an if-else construct.
for_statement, // After the 'for' token
for_statement_paren_open, // While inside the (...)
for_statement_init, // The initializer part of the for statement
for_statement_condition, // The condition part of the for statement
for_statement_expression, // The expression part of the for statement
switch_statement, // After 'switch' token
case_start, // after a 'case' or 'default' token
case_cont, // after the colon in a case/default
statement_with_condition, // A statement that takes a condition after the start token.
do_statement, // After 'do' token
return_statement, // After 'return'
block_open, // Statement block open brace.
substatement, // The first line after a conditional or loop construct.
substatement_open, // The brace that opens a substatement block.
arglist_open, // after the lparen. TODO: check if this is enough.
stream_op, // Lines continuing a stream operator (C++ only).
ternary_op, // The ? : operator
condition_open, // Start of a condition in 'if', 'while', entered after opening paren
condition_paren_open, // After an lparen in a condition
assign_open, // after an assignment token
expression, // after a '=' in a declaration_start once we're sure it's not '= {'
initializer, // after a '=' in a declaration start
};
struct State {
State()
: savedIndentDepth(0)
, type(0)
{}
State(quint8 ty, quint16 savedDepth)
: savedIndentDepth(savedDepth)
, type(ty)
{}
quint16 savedIndentDepth;
quint8 type;
bool operator==(const State &other) const {
return type == other.type
&& savedIndentDepth == other.savedIndentDepth;
}
};
State state(int belowTop = 0) const;
int tokenIndex() const;
int tokenIndexFromEnd() const;
const CPlusPlus::Token &currentToken() const;
const CPlusPlus::Token &tokenAt(int idx) const;
bool isBracelessState(int type) const;
void invalidateCache();
private:
void requireStatesUntil(const QTextBlock &block);
void recalculateStateAfter(const QTextBlock &block);
void storeBlockState(const QTextBlock &block);
void restoreBlockState(const QTextBlock &block);
QStringRef currentTokenText() const;
int tokenizeBlock(const QTextBlock &block, bool *endedJoined = 0);
void turnInto(int newState);
bool tryExpression(bool alsoExpression = false);
bool tryDeclaration();
bool tryStatement();
void enter(int newState);
void leave(bool statementDone = false);
void correctIndentation(const QTextBlock &block);
void dump();
private:
static QStack<State> initialState();
QPointer<QTextDocument> m_document;
QStack<State> m_beginState;
QStack<State> m_currentState;
QList<CPlusPlus::Token> m_tokens;
QString m_currentLine;
CPlusPlus::Token m_currentToken;
int m_tokenIndex;
// should store indent level and padding instead
int m_indentDepth;
friend class Internal::CppCodeFormatterData;
};
class CPPTOOLS_EXPORT QtStyleCodeFormatter : public CodeFormatter
{
public:
QtStyleCodeFormatter();
void setIndentSize(int size);
protected:
virtual void onEnter(int newState, int *indentDepth, int *savedIndentDepth) const;
virtual void adjustIndent(const QList<CPlusPlus::Token> &tokens, int lexerState, int *indentDepth) const;
private:
int m_indentSize;
};
} // namespace CppTools
#endif // CPPCODEFORMATTER_H
......@@ -23,7 +23,8 @@ HEADERS += completionsettingspage.h \
searchsymbols.h \
cppdoxygen.h \
cppfilesettingspage.h \
cppfindreferences.h
cppfindreferences.h \
cppcodeformatter.h
SOURCES += completionsettingspage.cpp \
cppclassesfilter.cpp \
......@@ -38,7 +39,8 @@ SOURCES += completionsettingspage.cpp \
cppdoxygen.cpp \
cppfilesettingspage.cpp \
abstracteditorsupport.cpp \
cppfindreferences.cpp
cppfindreferences.cpp \
cppcodeformatter.cpp
FORMS += completionsettingspage.ui \
cppfilesettingspage.ui
......
......@@ -31,6 +31,15 @@
using namespace TextEditor;
CodeFormatterData::CodeFormatterData(int blockRevision)
: m_blockRevision(blockRevision)
{
}
CodeFormatterData::~CodeFormatterData()
{
}
TextBlockUserData::~TextBlockUserData()
{
TextMarks marks = m_marks;
......@@ -38,6 +47,9 @@ TextBlockUserData::~TextBlockUserData()
foreach (ITextMark *mrk, marks) {
mrk->removedFromEditor();
}
if (m_codeFormatterData)
delete m_codeFormatterData;
}
int TextBlockUserData::braceDepthDelta() const
......@@ -359,6 +371,35 @@ TextBlockUserData::MatchType TextBlockUserData::matchCursorForward(QTextCursor *
return NoMatch;
}
int TextBlockUserData::lexerState(const QTextBlock &block)
{
if (!block.isValid())
return -1;
int data = block.userState();
if (data == -1)
return -1;
return data & 0xFF;
}
void TextBlockUserData::setLexerState(QTextBlock block, int state)
{
if (!block.isValid())
return;
int data = block.userState();
if (data == -1)
data = 0;
block.setUserState((data & ~0xFF) | (state & 0xFF));
}
void TextBlockUserData::setCodeFormatterData(CodeFormatterData *data)
{
if (m_codeFormatterData)
delete m_codeFormatterData;
m_codeFormatterData = data;
}
BaseTextDocumentLayout::BaseTextDocumentLayout(QTextDocument *doc)
:QPlainTextDocumentLayout(doc) {
......@@ -528,6 +569,3 @@ QSizeF BaseTextDocumentLayout::documentSize() const
size.setWidth(qMax((qreal)m_requiredWidth, size.width()));
return size;
}
......@@ -54,6 +54,18 @@ struct TEXTEDITOR_EXPORT Parenthesis
int pos;
};
class TEXTEDITOR_EXPORT CodeFormatterData
{
public:
CodeFormatterData(int blockRevision);
virtual ~CodeFormatterData();
int blockRevision() const { return m_blockRevision; }
void setBlockRevision(int revision) { m_blockRevision = revision; }
private:
int m_blockRevision;
};
class TEXTEDITOR_EXPORT TextBlockUserData : public QTextBlockUserData
{
......@@ -64,7 +76,9 @@ public:
m_ifdefedOut(false),
m_foldingIndent(0),
m_foldingStartIncluded(false),
m_foldingEndIncluded(false){}
m_foldingEndIncluded(false),
m_codeFormatterData(0)
{}
~TextBlockUserData();
inline TextMarks marks() const { return m_marks; }
......@@ -106,6 +120,11 @@ public:
void setFoldingEndIncluded(bool included) { m_foldingEndIncluded = included; }
bool foldingEndIncluded() const { return m_foldingEndIncluded; }
static int lexerState(const QTextBlock &block);
static void setLexerState(QTextBlock block, int state);
CodeFormatterData *codeFormatterData() const { return m_codeFormatterData; }
void setCodeFormatterData(CodeFormatterData *data);
private:
TextMarks m_marks;
......@@ -115,6 +134,7 @@ private:
uint m_foldingStartIncluded : 1;
uint m_foldingEndIncluded : 1;
Parentheses m_parentheses;
CodeFormatterData *m_codeFormatterData;
};
......
TEMPLATE = app
CONFIG += qt warn_on console depend_includepath
QT += testlib
include(../shared/shared.pri)
SRCDIR = ../../../../src
SOURCES += \
tst_codeformatter.cpp \
$$SRCDIR/plugins/cpptools/cppcodeformatter.cpp \
$$SRCDIR/plugins/texteditor/basetextdocumentlayout.cpp
HEADERS += \
$$SRCDIR/plugins/texteditor/basetextdocumentlayout.h
INCLUDEPATH += $$SRCDIR/plugins $$SRCDIR/libs
TARGET=tst_$$TARGET
#include <QtTest>
#include <QObject>
#include <QList>
#include <QTextDocument>
#include <QTextBlock>
#include <cpptools/cppcodeformatter.h>
using namespace CppTools;
class tst_CodeFormatter: public QObject
{
Q_OBJECT
private Q_SLOTS:
void ifStatementWithoutBraces1();
void ifStatementWithoutBraces2();
void ifStatementWithBraces1();
void ifStatementWithBraces2();
void ifStatementMixed();
void ifStatementAndComments();
void ifStatementLongCondition();
void strayElse();
void macrosNoSemicolon();
void oneLineIf();
void doWhile();
void closingCurlies();
void ifdefedInsideIf();
void ifdefs();
void preprocessorContinuation();
void cStyleComments();
void cppStyleComments();
void expressionContinuation();
void classAccess();
void ternary();
void objcAtDeclarations();
void braceList();
void bug1();
void bug2();
void switch1();
void memberInitializer();
void templates();
void operatorOverloads();
};
struct Line {
Line(QString l)
: line(l)
{
for (int i = 0; i < l.size(); ++i) {
if (!l.at(i).isSpace()) {
expectedIndent = i;
return;
}
}
expectedIndent = l.size();
}
Line(QString l, int expect)
: line(l), expectedIndent(expect)
{}
QString line;
int expectedIndent;
};
QString concatLines(QList<Line> lines)
{
QString result;
foreach (const Line &l, lines) {
result += l.line;
result += "\n";
}
return result;
}
void checkIndent(QList<Line> data)
{
QString text = concatLines(data);
QTextDocument document(text);
QtStyleCodeFormatter formatter;
formatter.setDocument(&document);
int i = 0;
foreach (const Line &l, data) {
if (l.expectedIndent != -1) {
int actualIndent = formatter.indentFor(document.findBlockByLineNumber(i));
if (actualIndent != l.expectedIndent) {
QFAIL(QString("Wrong indent in line %1 with text '%2', expected indent %3, got %4").arg(
QString::number(i+1), l.line, QString::number(l.expectedIndent), QString::number(actualIndent)).toLatin1().constData());
}
}
++i;
}
}
void tst_CodeFormatter::ifStatementWithoutBraces1()
{
QList<Line> data;
data << Line("void foo() {")
<< Line(" if (a)")
<< Line(" if (b)")
<< Line(" foo;")
<< Line(" else if (c)")
<< Line(" foo;")
<< Line(" else")
<< Line(" if (d)")
<< Line(" foo;")
<< Line(" else")
<< Line(" while (e)")
<< Line(" bar;")
<< Line(" else")
<< Line(" foo;")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::ifStatementWithoutBraces2()
{
QList<Line> data;
data << Line("void foo() {")
<< Line(" if (a)")
<< Line(" if (b)")
<< Line(" foo;")
<< Line(" if (a) b();")
<< Line(" if (a) b(); else")
<< Line(" foo;")
<< Line(" if (a)")
<< Line(" if (b)")
<< Line(" foo;")
<< Line(" else if (c)")
<< Line(" foo;")
<< Line(" else")
<< Line(" if (d)")
<< Line(" foo;")
<< Line(" else")
<< Line(" while (e)")
<< Line(" bar;")
<< Line(" else")
<< Line(" foo;")
<< Line("}")
;
checkIndent(data);
}
void tst_CodeFormatter::ifStatementWithBraces1()
{
QList<Line> data;
data << Line("void foo() {")
<< Line(" if (a) {")
<< Line(" if (b) {")
<< Line(" foo;")
<< Line(" } else if (c) {")
<< Line(" foo;")
<< Line(" } else {")
<< Line(" if (d) {")
<< Line(" foo;")
<< Line(" } else {")
<< Line(" foo;")
<< Line(" }")
<< Line(" }")
<< Line(" } else {")
<< Line(" foo;")
<< Line(" }")
<< Line("}");
checkIndent(data);
}
void tst_CodeFormatter::ifStatementWithBraces2()
{
QList<Line> data;
data << Line("void foo()")
<< Line("{")
<< Line(" if (a)")
<< Line(" {")
<< Line(" if (b)")
<< Line(" {")
<< Line(" foo;")
<< Line(" }")
<< Line(" else if (c)")
<< Line(" {")
<< Line(" foo;")
<< Line(" }")
<< Line(" else")
<< Line(" {")
<< Line(" if (d)")
<< Line(" {")
<< Line(" foo;")
<< Line(" }")
<< Line(" else")
<< Line(" {")
<< Line(" foo;")
<< Line(" }")
<< Line(" }")
<< Line(" }")
<< Line(" else")
<< Line(" {")
<< Line(" foo;")
<< Line(" }")
<< Line("}");
checkIndent(data);
}
void tst_CodeFormatter::ifStatementMixed()
{
QList<Line> data;
data << Line("void foo()")
<< Line("{")
<< Line(" if (foo)")
<< Line(" if (bar)")
<< Line(" {")
<< Line(" foo;")
<< Line(" }")
<< Line(" else")
<< Line(" if (car)")
<< Line(" {}")
<< Line(" else doo;")
<< Line(" else abc;")
<< Line("}");
checkIndent(data);
}
void tst_CodeFormatter::ifStatementAndComments()
{
QList<Line> data;
data << Line("void foo()")
<< Line("{")
<< Line(" if (foo)")
<< Line(" ; // bla")
<< Line(" else if (bar)")
<< Line(" ;")
<< Line(" if (foo)")
<< Line(" ; /* bla")
<< Line(" bla */")
<< Line(" else if (bar)")
<< Line(" // foobar")
<< Line(" ;")
<< Line(" else if (bar)")
<< Line(" /* bla")
<< Line(" bla */")
<< Line(" ;")
<< Line("}");
checkIndent(data);
}
void tst_CodeFormatter::ifStatementLongCondition()
{