forked from qt-creator/qt-creator
CppEditor: Add quickfix for converting comments from C++ to C style
... and vice versa. Fixes: QTCREATORBUG-27501 Change-Id: I8584cc1e86718b3fe0f0ead2a3436495303ca3c8 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
73
src/libs/3rdparty/cplusplus/TranslationUnit.cpp
vendored
73
src/libs/3rdparty/cplusplus/TranslationUnit.cpp
vendored
@@ -105,6 +105,35 @@ int TranslationUnit::commentCount() const
|
|||||||
const Token &TranslationUnit::commentAt(int index) const
|
const Token &TranslationUnit::commentAt(int index) const
|
||||||
{ return _comments->at(index); }
|
{ return _comments->at(index); }
|
||||||
|
|
||||||
|
std::vector<Token> TranslationUnit::allTokens() const
|
||||||
|
{
|
||||||
|
std::vector<Token> all;
|
||||||
|
int tokIndex = 0;
|
||||||
|
int commentIndex = 0;
|
||||||
|
while (true) {
|
||||||
|
if (tokIndex == tokenCount()) {
|
||||||
|
for (int i = commentIndex; i < commentCount(); ++i)
|
||||||
|
all.push_back(commentAt(i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (commentIndex == commentCount()) {
|
||||||
|
for (int i = tokIndex; i < tokenCount(); ++i)
|
||||||
|
all.push_back(tokenAt(i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const Token &tok = tokenAt(tokIndex);
|
||||||
|
const Token &comment = commentAt(commentIndex);
|
||||||
|
if (tok.utf16charsBegin() < comment.utf16charsBegin()) {
|
||||||
|
all.push_back(tok);
|
||||||
|
++tokIndex;
|
||||||
|
} else {
|
||||||
|
all.push_back(comment);
|
||||||
|
++commentIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
const Identifier *TranslationUnit::identifier(int index) const
|
const Identifier *TranslationUnit::identifier(int index) const
|
||||||
{ return tokenAt(index).identifier; }
|
{ return tokenAt(index).identifier; }
|
||||||
|
|
||||||
@@ -381,27 +410,55 @@ int TranslationUnit::findColumnNumber(int utf16CharOffset, int lineNumber) const
|
|||||||
|
|
||||||
int TranslationUnit::getTokenPositionInDocument(int index, const QTextDocument *doc) const
|
int TranslationUnit::getTokenPositionInDocument(int index, const QTextDocument *doc) const
|
||||||
{
|
{
|
||||||
int line, column;
|
return getTokenPositionInDocument(_tokens->at(index), doc);
|
||||||
getTokenPosition(index, &line, &column);
|
|
||||||
return Utils::Text::positionInText(doc, line, column);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int TranslationUnit::getTokenEndPositionInDocument(int index, const QTextDocument *doc) const
|
int TranslationUnit::getTokenEndPositionInDocument(int index, const QTextDocument *doc) const
|
||||||
{
|
{
|
||||||
int line, column;
|
return getTokenEndPositionInDocument(_tokens->at(index), doc);
|
||||||
getTokenEndPosition(index, &line, &column);
|
|
||||||
return Utils::Text::positionInText(doc, line, column);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TranslationUnit::getTokenPosition(int index, int *line,
|
void TranslationUnit::getTokenPosition(int index, int *line,
|
||||||
int *column,
|
int *column,
|
||||||
const StringLiteral **fileName) const
|
const StringLiteral **fileName) const
|
||||||
{ return getPosition(tokenAt(index).utf16charsBegin(), line, column, fileName); }
|
{
|
||||||
|
return getTokenPosition(_tokens->at(index), line, column, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
void TranslationUnit::getTokenEndPosition(int index, int *line,
|
void TranslationUnit::getTokenEndPosition(int index, int *line,
|
||||||
int *column,
|
int *column,
|
||||||
const StringLiteral **fileName) const
|
const StringLiteral **fileName) const
|
||||||
{ return getPosition(tokenAt(index).utf16charsEnd(), line, column, fileName); }
|
{
|
||||||
|
return getTokenEndPosition(_tokens->at(index), line, column, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslationUnit::getTokenPosition(const Token &token, int *line, int *column,
|
||||||
|
const StringLiteral **fileName) const
|
||||||
|
{
|
||||||
|
return getPosition(token.utf16charsBegin(), line, column, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslationUnit::getTokenEndPosition(const Token &token, int *line,
|
||||||
|
int *column, const StringLiteral **fileName) const
|
||||||
|
{
|
||||||
|
return getPosition(token.utf16charsEnd(), line, column, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TranslationUnit::getTokenPositionInDocument(const Token token,
|
||||||
|
const QTextDocument *doc) const
|
||||||
|
{
|
||||||
|
int line, column;
|
||||||
|
getTokenPosition(token, &line, &column);
|
||||||
|
return Utils::Text::positionInText(doc, line, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TranslationUnit::getTokenEndPositionInDocument(const Token &token,
|
||||||
|
const QTextDocument *doc) const
|
||||||
|
{
|
||||||
|
int line, column;
|
||||||
|
getTokenEndPosition(token, &line, &column);
|
||||||
|
return Utils::Text::positionInText(doc, line, column);
|
||||||
|
}
|
||||||
|
|
||||||
void TranslationUnit::getPosition(int utf16charOffset,
|
void TranslationUnit::getPosition(int utf16charOffset,
|
||||||
int *line,
|
int *line,
|
||||||
|
|||||||
11
src/libs/3rdparty/cplusplus/TranslationUnit.h
vendored
11
src/libs/3rdparty/cplusplus/TranslationUnit.h
vendored
@@ -65,6 +65,9 @@ public:
|
|||||||
int commentCount() const;
|
int commentCount() const;
|
||||||
const Token &commentAt(int index) const;
|
const Token &commentAt(int index) const;
|
||||||
|
|
||||||
|
// Including comments.
|
||||||
|
std::vector<Token> allTokens() const;
|
||||||
|
|
||||||
int matchingBrace(int index) const;
|
int matchingBrace(int index) const;
|
||||||
const Identifier *identifier(int index) const;
|
const Identifier *identifier(int index) const;
|
||||||
const Literal *literal(int index) const;
|
const Literal *literal(int index) const;
|
||||||
@@ -120,9 +123,17 @@ public:
|
|||||||
int *line,
|
int *line,
|
||||||
int *column = nullptr,
|
int *column = nullptr,
|
||||||
const StringLiteral **fileName = nullptr) const;
|
const StringLiteral **fileName = nullptr) const;
|
||||||
|
|
||||||
int getTokenPositionInDocument(int index, const QTextDocument *doc) const;
|
int getTokenPositionInDocument(int index, const QTextDocument *doc) const;
|
||||||
int getTokenEndPositionInDocument(int index, const QTextDocument *doc) const;
|
int getTokenEndPositionInDocument(int index, const QTextDocument *doc) const;
|
||||||
|
|
||||||
|
void getTokenPosition(const Token &token, int *line, int *column = nullptr,
|
||||||
|
const StringLiteral **fileName = nullptr) const;
|
||||||
|
void getTokenEndPosition(const Token &token, int *line, int *column = nullptr,
|
||||||
|
const StringLiteral **fileName = nullptr) const;
|
||||||
|
int getTokenPositionInDocument(const Token token, const QTextDocument *doc) const;
|
||||||
|
int getTokenEndPositionInDocument(const Token &token, const QTextDocument *doc) const;
|
||||||
|
|
||||||
void pushLineOffset(int offset);
|
void pushLineOffset(int offset);
|
||||||
void pushPreprocessorLine(int utf16charOffset,
|
void pushPreprocessorLine(int utf16charOffset,
|
||||||
int line,
|
int line,
|
||||||
|
|||||||
@@ -8982,4 +8982,201 @@ void QuickfixTest::testGenerateConstructor()
|
|||||||
QuickFixOperationTest(testDocuments, &factory);
|
QuickFixOperationTest(testDocuments, &factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QuickfixTest::testChangeCommentType_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("input");
|
||||||
|
QTest::addColumn<QString>("expectedOutput");
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / no selection / single line") << R"(
|
||||||
|
int var1;
|
||||||
|
/* Other comment, unaffected */
|
||||||
|
/* Our @comment */
|
||||||
|
/* Another unaffected comment */
|
||||||
|
int var2;)" << R"(
|
||||||
|
int var1;
|
||||||
|
/* Other comment, unaffected */
|
||||||
|
// Our comment
|
||||||
|
/* Another unaffected comment */
|
||||||
|
int var2;)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / no selection / multi-line / preserved header and footer") << R"(
|
||||||
|
/****************************************************
|
||||||
|
* some info
|
||||||
|
* more @info
|
||||||
|
***************************************************/)" << R"(
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// some info
|
||||||
|
// more info
|
||||||
|
/////////////////////////////////////////////////////)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / no selection / multi-line / non-preserved header and footer") << R"(
|
||||||
|
/*
|
||||||
|
* some info
|
||||||
|
* more @info
|
||||||
|
*/)" << R"(
|
||||||
|
// some info
|
||||||
|
// more info
|
||||||
|
)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / no selection / qdoc") << R"(
|
||||||
|
/*!
|
||||||
|
\qmlproperty string Type::element.name
|
||||||
|
\qmlproperty int Type::element.id
|
||||||
|
|
||||||
|
\brief Holds the @element name and id.
|
||||||
|
*/)" << R"(
|
||||||
|
//! \qmlproperty string Type::element.name
|
||||||
|
//! \qmlproperty @int Type::element.id
|
||||||
|
//!
|
||||||
|
//! \brief Holds the element name and id.
|
||||||
|
)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / no selection / doxygen") << R"(
|
||||||
|
/*! \class Test
|
||||||
|
\brief A test class.
|
||||||
|
|
||||||
|
A more detailed @class description.
|
||||||
|
*/)" << R"(
|
||||||
|
//! \class Test
|
||||||
|
//! \brief A test class.
|
||||||
|
//!
|
||||||
|
//! A more detailed class description.
|
||||||
|
)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / selection / single line") << R"(
|
||||||
|
int var1;
|
||||||
|
/* Other comment, unaffected */
|
||||||
|
@{start}/* Our comment */@{end}
|
||||||
|
/* Another unaffected comment */
|
||||||
|
int var2;)" << R"(
|
||||||
|
int var1;
|
||||||
|
/* Other comment, unaffected */
|
||||||
|
// Our comment
|
||||||
|
/* Another unaffected comment */
|
||||||
|
int var2;)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / selection / multi-line / preserved header and footer") << R"(
|
||||||
|
/****************************************************
|
||||||
|
* @{start}some info
|
||||||
|
* more info@{end}
|
||||||
|
***************************************************/)" << R"(
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// some info
|
||||||
|
// more info
|
||||||
|
/////////////////////////////////////////////////////)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / selection / multi-line / non-preserved header and footer") << R"(
|
||||||
|
/*@{start}
|
||||||
|
* some in@{end}fo
|
||||||
|
* more info
|
||||||
|
*/)" << R"(
|
||||||
|
// some info
|
||||||
|
// more info
|
||||||
|
)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / selection / qdoc") << R"(
|
||||||
|
/*!@{start}
|
||||||
|
\qmlproperty string Type::element.name
|
||||||
|
\qmlproperty int Type::element.id
|
||||||
|
|
||||||
|
\brief Holds the element name and id.
|
||||||
|
*/@{end})" << R"(
|
||||||
|
//! \qmlproperty string Type::element.name
|
||||||
|
//! \qmlproperty int Type::element.id
|
||||||
|
//!
|
||||||
|
//! \brief Holds the element name and id.
|
||||||
|
)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / selection / doxygen") << R"(
|
||||||
|
/** Expand envi@{start}ronment variables in a string.
|
||||||
|
*
|
||||||
|
* Environment variables are accepted in the @{end}following forms:
|
||||||
|
* $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows.
|
||||||
|
* No escapes and quoting are supported.
|
||||||
|
* If a variable is not found, it is not substituted.
|
||||||
|
*/)" << R"(
|
||||||
|
//! Expand environment variables in a string.
|
||||||
|
//!
|
||||||
|
//! Environment variables are accepted in the following forms:
|
||||||
|
//! $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows.
|
||||||
|
//! No escapes and quoting are supported.
|
||||||
|
//! If a variable is not found, it is not substituted.
|
||||||
|
)";
|
||||||
|
|
||||||
|
QTest::newRow("C -> C++ / selection / multiple comments") << R"(
|
||||||
|
@{start}/* Affected comment */
|
||||||
|
/* Another affected comment */
|
||||||
|
/* A third affected comment */@{end}
|
||||||
|
/* An unaffected comment */)" << R"(
|
||||||
|
// Affected comment
|
||||||
|
// Another affected comment
|
||||||
|
// A third affected comment
|
||||||
|
/* An unaffected comment */)";
|
||||||
|
|
||||||
|
QTest::newRow("C++ -> C / no selection / single line") << R"(
|
||||||
|
// Other comment, unaffected
|
||||||
|
// Our @comment
|
||||||
|
// Another unaffected comment)" << R"(
|
||||||
|
// Other comment, unaffected
|
||||||
|
/* Our comment */
|
||||||
|
// Another unaffected comment)";
|
||||||
|
|
||||||
|
QTest::newRow("C++ -> C / selection / single line") << R"(
|
||||||
|
// Other comment, unaffected
|
||||||
|
@{start}// Our comment@{end}
|
||||||
|
// Another unaffected comment)" << R"(
|
||||||
|
// Other comment, unaffected
|
||||||
|
/* Our comment */
|
||||||
|
// Another unaffected comment)";
|
||||||
|
|
||||||
|
QTest::newRow("C++ -> C / selection / multi-line / preserved header and footer") << R"(
|
||||||
|
@{start}/////////////////////////////////////////////////////
|
||||||
|
// some info
|
||||||
|
// more info
|
||||||
|
/////////////////////////////////////////////////////@{end})" << R"(
|
||||||
|
/****************************************************/
|
||||||
|
/* some info */
|
||||||
|
/* more info */
|
||||||
|
/****************************************************/)";
|
||||||
|
|
||||||
|
QTest::newRow("C++ -> C / selection / qdoc") << R"(
|
||||||
|
@{start}//! \qmlproperty string Type::element.name
|
||||||
|
//! \qmlproperty int Type::element.id
|
||||||
|
//!
|
||||||
|
//! \brief Holds the element name and id.@{end}
|
||||||
|
)" << R"(
|
||||||
|
/*!
|
||||||
|
\qmlproperty string Type::element.name
|
||||||
|
\qmlproperty int Type::element.id
|
||||||
|
|
||||||
|
\brief Holds the element name and id.
|
||||||
|
*/
|
||||||
|
)";
|
||||||
|
|
||||||
|
QTest::newRow("C++ -> C / selection / doxygen") << R"(
|
||||||
|
@{start}//! \class Test
|
||||||
|
//! \brief A test class.
|
||||||
|
//!
|
||||||
|
//! A more detailed class description.@{end}
|
||||||
|
)" << R"(
|
||||||
|
/*!
|
||||||
|
\class Test
|
||||||
|
\brief A test class.
|
||||||
|
|
||||||
|
A more detailed class description.
|
||||||
|
*/
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuickfixTest::testChangeCommentType()
|
||||||
|
{
|
||||||
|
QFETCH(QString, input);
|
||||||
|
QFETCH(QString, expectedOutput);
|
||||||
|
|
||||||
|
ConvertCommentStyle factory;
|
||||||
|
QuickFixOperationTest(
|
||||||
|
{CppTestDocument::create("file.h", input.toUtf8(), expectedOutput.toUtf8())},
|
||||||
|
&factory);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace CppEditor::Internal::Tests
|
} // namespace CppEditor::Internal::Tests
|
||||||
|
|||||||
@@ -219,6 +219,9 @@ private slots:
|
|||||||
|
|
||||||
void testGenerateConstructor_data();
|
void testGenerateConstructor_data();
|
||||||
void testGenerateConstructor();
|
void testGenerateConstructor();
|
||||||
|
|
||||||
|
void testChangeCommentType_data();
|
||||||
|
void testChangeCommentType();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Tests
|
} // namespace Tests
|
||||||
|
|||||||
@@ -9306,6 +9306,231 @@ void GenerateConstructor::match(const CppQuickFixInterface &interface, QuickFixO
|
|||||||
result << op;
|
result << op;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class ConvertCommentStyleOp : public CppQuickFixOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ConvertCommentStyleOp(const CppQuickFixInterface &interface, const QList<Token> &tokens,
|
||||||
|
Kind kind)
|
||||||
|
: CppQuickFixOperation(interface),
|
||||||
|
m_tokens(tokens),
|
||||||
|
m_kind(kind),
|
||||||
|
m_wasCxxStyle(m_kind == T_CPP_COMMENT || m_kind == T_CPP_DOXY_COMMENT),
|
||||||
|
m_isDoxygen(m_kind == T_DOXY_COMMENT || m_kind == T_CPP_DOXY_COMMENT)
|
||||||
|
{
|
||||||
|
setDescription(m_wasCxxStyle ? Tr::tr("Convert comment to C style")
|
||||||
|
: Tr::tr("Convert comment to C++ style"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Turns every line of a C-style comment into a C++-style comment and vice versa.
|
||||||
|
// For C++ -> C, we use one /* */ comment block per line. However, doxygen
|
||||||
|
// requires a single comment, so there we just replace the prefix with whitespace and
|
||||||
|
// add the start and end comment in extra lines.
|
||||||
|
// For cosmetic reasons, we offer some convenience functionality:
|
||||||
|
// - Turn /***** ... into ////// ... and vice versa
|
||||||
|
// - With C -> C++, remove leading asterisks.
|
||||||
|
// - With C -> C++, remove the first and last line of a block if they have no content
|
||||||
|
// other than the comment start and end characters.
|
||||||
|
// - With C++ -> C, try to align the end comment characters.
|
||||||
|
// These are obviously heuristics; we do not guarantee perfect results for everybody.
|
||||||
|
// We also don't second-guess the users's selection: E.g. if there is an empty
|
||||||
|
// line between the tokens, then it's not the same doxygen comment, but we merge
|
||||||
|
// it anyway in C++ to C mode.
|
||||||
|
void perform() override
|
||||||
|
{
|
||||||
|
TranslationUnit * const tu = currentFile()->cppDocument()->translationUnit();
|
||||||
|
const QString newCommentStart = getNewCommentStart();
|
||||||
|
ChangeSet changeSet;
|
||||||
|
int endCommentColumn = -1;
|
||||||
|
const QChar oldFillChar = m_wasCxxStyle ? '/' : '*';
|
||||||
|
const QChar newFillChar = m_wasCxxStyle ? '*' : '/';
|
||||||
|
|
||||||
|
for (const Token &token : m_tokens) {
|
||||||
|
const int startPos = tu->getTokenPositionInDocument(token, textDocument());
|
||||||
|
const int endPos = tu->getTokenEndPositionInDocument(token, textDocument());
|
||||||
|
|
||||||
|
if (m_wasCxxStyle && m_isDoxygen) {
|
||||||
|
// Replace "///" characters with whitespace (to keep alignment).
|
||||||
|
// The insertion of "/*" and "*/" is done once after the loop.
|
||||||
|
changeSet.replace(startPos, startPos + 3, " ");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QTextBlock firstBlock = textDocument()->findBlock(startPos);
|
||||||
|
const QTextBlock lastBlock = textDocument()->findBlock(endPos);
|
||||||
|
for (QTextBlock block = firstBlock; block.isValid() && block.position() <= endPos;
|
||||||
|
block = block.next()) {
|
||||||
|
const QString &blockText = block.text();
|
||||||
|
const int firstColumn = block == firstBlock ? startPos - block.position() : 0;
|
||||||
|
const int endColumn = block == lastBlock ? endPos - block.position()
|
||||||
|
: block.length();
|
||||||
|
|
||||||
|
// Returns true if the current line looks like "/********/" or "//////////",
|
||||||
|
// as is often the case at the start and end of comment blocks.
|
||||||
|
const auto fillChecker = [&] {
|
||||||
|
if (m_isDoxygen)
|
||||||
|
return false;
|
||||||
|
QString textToCheck = blockText;
|
||||||
|
if (block == firstBlock)
|
||||||
|
textToCheck.remove(0, 1);
|
||||||
|
if (block == lastBlock)
|
||||||
|
textToCheck.chop(block.length() - endColumn);
|
||||||
|
return Utils::allOf(textToCheck, [oldFillChar](const QChar &c)
|
||||||
|
{ return c == oldFillChar || c == ' ';
|
||||||
|
}) && textToCheck.count(oldFillChar) > 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns the index of the first character of actual comment content,
|
||||||
|
// as opposed to visual stuff like slashes, stars or whitespace.
|
||||||
|
const auto indexOfActualContent = [&] {
|
||||||
|
const int offset = block == firstBlock ? firstColumn + newCommentStart.length()
|
||||||
|
: firstColumn;
|
||||||
|
|
||||||
|
for (int i = offset, lastFillChar = -1; i < blockText.length(); ++i) {
|
||||||
|
if (blockText.at(i) == oldFillChar) {
|
||||||
|
lastFillChar = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!blockText.at(i).isSpace())
|
||||||
|
return lastFillChar + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fillChecker()) {
|
||||||
|
const QString replacement = QString(endColumn - 1 - firstColumn, newFillChar);
|
||||||
|
changeSet.replace(block.position() + firstColumn,
|
||||||
|
block.position() + endColumn - 1,
|
||||||
|
replacement);
|
||||||
|
if (m_wasCxxStyle) {
|
||||||
|
changeSet.replace(block.position() + firstColumn,
|
||||||
|
block.position() + firstColumn + 1, "/");
|
||||||
|
changeSet.insert(block.position() + endColumn - 1, "*");
|
||||||
|
endCommentColumn = endColumn - 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading noise or even the entire block, if applicable.
|
||||||
|
const bool blockIsRemovable = (block == firstBlock || block == lastBlock)
|
||||||
|
&& firstBlock != lastBlock;
|
||||||
|
const auto removeBlock = [&] {
|
||||||
|
changeSet.remove(block.position() + firstColumn, block.position() + endColumn);
|
||||||
|
};
|
||||||
|
const int contentIndex = indexOfActualContent();
|
||||||
|
if (contentIndex == -1) {
|
||||||
|
if (blockIsRemovable) {
|
||||||
|
removeBlock();
|
||||||
|
continue;
|
||||||
|
} else if (!m_wasCxxStyle) {
|
||||||
|
changeSet.replace(block.position() + firstColumn,
|
||||||
|
block.position() + endColumn - 1, newCommentStart);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (block == lastBlock && contentIndex == endColumn - 1) {
|
||||||
|
if (blockIsRemovable) {
|
||||||
|
removeBlock();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changeSet.remove(block.position() + firstColumn,
|
||||||
|
block.position() + firstColumn + contentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block == firstBlock) {
|
||||||
|
changeSet.replace(startPos, startPos + newCommentStart.length(),
|
||||||
|
newCommentStart);
|
||||||
|
} else {
|
||||||
|
// If the line starts with enough whitespace, replace it with the
|
||||||
|
// comment start characters, so we don't move the content to the right
|
||||||
|
// unnecessarily. Otherwise, insert the comment start characters.
|
||||||
|
if (blockText.startsWith(QString(newCommentStart.size() + 1, ' '))) {
|
||||||
|
changeSet.replace(block.position(),
|
||||||
|
block.position() + newCommentStart.length(),
|
||||||
|
newCommentStart);
|
||||||
|
} else {
|
||||||
|
changeSet.insert(block.position(), newCommentStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block == lastBlock) {
|
||||||
|
if (m_wasCxxStyle) {
|
||||||
|
// This is for proper alignment of the end comment character.
|
||||||
|
if (endCommentColumn != -1) {
|
||||||
|
const int endCommentPos = block.position() + endCommentColumn;
|
||||||
|
if (endPos < endCommentPos)
|
||||||
|
changeSet.insert(endPos, QString(endCommentPos - endPos - 1, ' '));
|
||||||
|
}
|
||||||
|
changeSet.insert(endPos, " */");
|
||||||
|
} else {
|
||||||
|
changeSet.remove(endPos - 2, endPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_wasCxxStyle && m_isDoxygen) {
|
||||||
|
const int startPos = tu->getTokenPositionInDocument(m_tokens.first(), textDocument());
|
||||||
|
const int endPos = tu->getTokenEndPositionInDocument(m_tokens.last(), textDocument());
|
||||||
|
changeSet.insert(startPos, "/*!\n");
|
||||||
|
changeSet.insert(endPos, "\n*/");
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSet.apply(textDocument());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getNewCommentStart() const
|
||||||
|
{
|
||||||
|
if (m_wasCxxStyle) {
|
||||||
|
if (m_isDoxygen)
|
||||||
|
return "/*!";
|
||||||
|
return "/*";
|
||||||
|
}
|
||||||
|
if (m_isDoxygen)
|
||||||
|
return "//!";
|
||||||
|
return "//";
|
||||||
|
}
|
||||||
|
|
||||||
|
const QList<Token> m_tokens;
|
||||||
|
const Kind m_kind;
|
||||||
|
const bool m_wasCxxStyle;
|
||||||
|
const bool m_isDoxygen;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void ConvertCommentStyle::match(const CppQuickFixInterface &interface,
|
||||||
|
TextEditor::QuickFixOperations &result)
|
||||||
|
{
|
||||||
|
// If there's a selection, then it must entirely consist of comment tokens.
|
||||||
|
// If there's no selection, the cursor must be on a comment.
|
||||||
|
const QList<Token> &cursorTokens = interface.currentFile()->tokensForCursor();
|
||||||
|
if (cursorTokens.empty())
|
||||||
|
return;
|
||||||
|
if (!cursorTokens.front().isComment())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// All tokens must be the same kind of comment, but we make an exception for doxygen comments
|
||||||
|
// that start with "///", as these are often not intended to be doxygen. For our purposes,
|
||||||
|
// we treat them as normal comments.
|
||||||
|
const auto effectiveKind = [&interface](const Token &token) {
|
||||||
|
if (token.kind() != T_CPP_DOXY_COMMENT)
|
||||||
|
return token.kind();
|
||||||
|
TranslationUnit * const tu = interface.currentFile()->cppDocument()->translationUnit();
|
||||||
|
const int startPos = tu->getTokenPositionInDocument(token, interface.textDocument());
|
||||||
|
const QString commentStart = interface.textAt(startPos, 3);
|
||||||
|
return commentStart == "///" ? T_CPP_COMMENT : T_CPP_DOXY_COMMENT;
|
||||||
|
};
|
||||||
|
const Kind kind = effectiveKind(cursorTokens.first());
|
||||||
|
for (int i = 1; i < cursorTokens.count(); ++i) {
|
||||||
|
if (effectiveKind(cursorTokens.at(i)) != kind)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok, all tokens are of same(ish) comment type, offer quickfix.
|
||||||
|
result << new ConvertCommentStyleOp(interface, cursorTokens, kind);
|
||||||
|
}
|
||||||
|
|
||||||
void createCppQuickFixes()
|
void createCppQuickFixes()
|
||||||
{
|
{
|
||||||
new AddIncludeForUndefinedIdentifier;
|
new AddIncludeForUndefinedIdentifier;
|
||||||
@@ -9362,6 +9587,7 @@ void createCppQuickFixes()
|
|||||||
|
|
||||||
new RemoveUsingNamespace;
|
new RemoveUsingNamespace;
|
||||||
new GenerateConstructor;
|
new GenerateConstructor;
|
||||||
|
new ConvertCommentStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroyCppQuickFixes()
|
void destroyCppQuickFixes()
|
||||||
|
|||||||
@@ -585,5 +585,13 @@ private:
|
|||||||
bool m_test = false;
|
bool m_test = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//! Converts C-style to C++-style comments and vice versa
|
||||||
|
class ConvertCommentStyle : public CppQuickFixFactory
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
void match(const CppQuickFixInterface &interface,
|
||||||
|
TextEditor::QuickFixOperations &result) override;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace CppEditor
|
} // namespace CppEditor
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
using namespace CPlusPlus;
|
using namespace CPlusPlus;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
@@ -138,6 +140,31 @@ bool CppRefactoringFile::isCursorOn(const AST *ast) const
|
|||||||
return cursorBegin >= start && cursorBegin <= end;
|
return cursorBegin >= start && cursorBegin <= end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<Token> CppRefactoringFile::tokensForCursor() const
|
||||||
|
{
|
||||||
|
QTextCursor c = cursor();
|
||||||
|
int pos = c.selectionStart();
|
||||||
|
int endPos = c.selectionEnd();
|
||||||
|
if (pos > endPos)
|
||||||
|
std::swap(pos, endPos);
|
||||||
|
|
||||||
|
const std::vector<Token> &allTokens = m_cppDocument->translationUnit()->allTokens();
|
||||||
|
const int firstIndex = tokenIndexForPosition(allTokens, pos, 0);
|
||||||
|
if (firstIndex == -1)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const int lastIndex = pos == endPos
|
||||||
|
? firstIndex
|
||||||
|
: tokenIndexForPosition(allTokens, endPos, firstIndex);
|
||||||
|
if (lastIndex == -1)
|
||||||
|
return {};
|
||||||
|
QTC_ASSERT(lastIndex >= firstIndex, return {});
|
||||||
|
QList<Token> result;
|
||||||
|
for (int i = firstIndex; i <= lastIndex; ++i)
|
||||||
|
result.push_back(allTokens.at(i));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
ChangeSet::Range CppRefactoringFile::range(unsigned tokenIndex) const
|
ChangeSet::Range CppRefactoringFile::range(unsigned tokenIndex) const
|
||||||
{
|
{
|
||||||
const Token &token = tokenAt(tokenIndex);
|
const Token &token = tokenAt(tokenIndex);
|
||||||
@@ -215,6 +242,29 @@ void CppRefactoringFile::fileChanged()
|
|||||||
RefactoringFile::fileChanged();
|
RefactoringFile::fileChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CppRefactoringFile::tokenIndexForPosition(const std::vector<CPlusPlus::Token> &tokens,
|
||||||
|
int pos, int startIndex) const
|
||||||
|
{
|
||||||
|
const TranslationUnit * const tu = m_cppDocument->translationUnit();
|
||||||
|
|
||||||
|
// Binary search
|
||||||
|
for (int l = startIndex, u = int(tokens.size()) - 1; l <= u; ) {
|
||||||
|
const int i = (l + u) / 2;
|
||||||
|
const int tokenPos = tu->getTokenPositionInDocument(tokens.at(i), document());
|
||||||
|
if (pos < tokenPos) {
|
||||||
|
u = i - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const int tokenEndPos = tu->getTokenEndPositionInDocument(tokens.at(i), document());
|
||||||
|
if (pos > tokenEndPos) {
|
||||||
|
l = i + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
CppRefactoringChangesData::CppRefactoringChangesData(const Snapshot &snapshot)
|
CppRefactoringChangesData::CppRefactoringChangesData(const Snapshot &snapshot)
|
||||||
: m_snapshot(snapshot)
|
: m_snapshot(snapshot)
|
||||||
, m_workingCopy(CppModelManager::workingCopy())
|
, m_workingCopy(CppModelManager::workingCopy())
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
#include <texteditor/refactoringchanges.h>
|
#include <texteditor/refactoringchanges.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace CppEditor {
|
namespace CppEditor {
|
||||||
|
|
||||||
class CppRefactoringChanges;
|
class CppRefactoringChanges;
|
||||||
@@ -44,6 +46,8 @@ public:
|
|||||||
|
|
||||||
void startAndEndOf(unsigned index, int *start, int *end) const;
|
void startAndEndOf(unsigned index, int *start, int *end) const;
|
||||||
|
|
||||||
|
QList<CPlusPlus::Token> tokensForCursor() const;
|
||||||
|
|
||||||
using TextEditor::RefactoringFile::textOf;
|
using TextEditor::RefactoringFile::textOf;
|
||||||
QString textOf(const CPlusPlus::AST *ast) const;
|
QString textOf(const CPlusPlus::AST *ast) const;
|
||||||
|
|
||||||
@@ -55,6 +59,9 @@ protected:
|
|||||||
CppRefactoringChangesData *data() const;
|
CppRefactoringChangesData *data() const;
|
||||||
void fileChanged() override;
|
void fileChanged() override;
|
||||||
|
|
||||||
|
int tokenIndexForPosition(const std::vector<CPlusPlus::Token> &tokens, int pos,
|
||||||
|
int startIndex) const;
|
||||||
|
|
||||||
mutable CPlusPlus::Document::Ptr m_cppDocument;
|
mutable CPlusPlus::Document::Ptr m_cppDocument;
|
||||||
|
|
||||||
friend class CppRefactoringChanges; // for access to constructor
|
friend class CppRefactoringChanges; // for access to constructor
|
||||||
|
|||||||
Reference in New Issue
Block a user