forked from qt-creator/qt-creator
CppEditor: Add escape/unescape string literal QuickFix
Change-Id: I32c22dfa32ee0345b76e8c35381bce988d20ed49 Reviewed-by: Nikolai Kosjar <nikolai.kosjar@digia.com>
This commit is contained in:
committed by
Nikolai Kosjar
parent
ed3cdb9c92
commit
66feceacb0
@@ -2060,6 +2060,18 @@
|
|||||||
\endcode
|
\endcode
|
||||||
\li for
|
\li for
|
||||||
|
|
||||||
|
\row
|
||||||
|
\li Escape String Literal as UTF-8
|
||||||
|
\li Escapes non-ASCII characters in a string literal to hexadecimal escape sequences.
|
||||||
|
String Literals are handled as UTF-8.
|
||||||
|
\li String literal
|
||||||
|
|
||||||
|
\row
|
||||||
|
\li Unescape String Literal as UTF-8
|
||||||
|
\li Unescapes octal or hexadecimal escape sequences in a string literal.
|
||||||
|
String Literals are handled as UTF-8.
|
||||||
|
\li String literal
|
||||||
|
|
||||||
\endtable
|
\endtable
|
||||||
|
|
||||||
\section2 Refactoring QML Code
|
\section2 Refactoring QML Code
|
||||||
|
|||||||
@@ -1271,6 +1271,43 @@ void CppEditorPlugin::test_quickfix_data()
|
|||||||
" int m_it;\n"
|
" int m_it;\n"
|
||||||
"};\n"
|
"};\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Escape String Literal as UTF-8 (no-trigger)
|
||||||
|
QTest::newRow("EscapeStringLiteral_notrigger")
|
||||||
|
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
|
||||||
|
<< _("const char *notrigger = \"@abcdef \\a\\n\\\\\";\n")
|
||||||
|
<< _();
|
||||||
|
|
||||||
|
// Escape String Literal as UTF-8
|
||||||
|
QTest::newRow("EscapeStringLiteral")
|
||||||
|
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
|
||||||
|
<< _("const char *utf8 = \"@\xe3\x81\x82\xe3\x81\x84\";\n")
|
||||||
|
<< _("const char *utf8 = \"\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n");
|
||||||
|
|
||||||
|
// Unescape String Literal as UTF-8 (from hexdecimal escape sequences)
|
||||||
|
QTest::newRow("UnescapeStringLiteral_hex")
|
||||||
|
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
|
||||||
|
<< _("const char *hex_escaped = \"@\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n")
|
||||||
|
<< _("const char *hex_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
|
||||||
|
|
||||||
|
// Unescape String Literal as UTF-8 (from octal escape sequences)
|
||||||
|
QTest::newRow("UnescapeStringLiteral_oct")
|
||||||
|
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
|
||||||
|
<< _("const char *oct_escaped = \"@\\343\\201\\202\\343\\201\\204\";\n")
|
||||||
|
<< _("const char *oct_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n");
|
||||||
|
|
||||||
|
// Unescape String Literal as UTF-8 (triggered but no change)
|
||||||
|
QTest::newRow("UnescapeStringLiteral_noconv")
|
||||||
|
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
|
||||||
|
<< _("const char *escaped_ascii = \"@\\x1b\";\n")
|
||||||
|
<< _("const char *escaped_ascii = \"\\x1b\";\n");
|
||||||
|
|
||||||
|
// Unescape String Literal as UTF-8 (no conversion because of invalid utf-8)
|
||||||
|
QTest::newRow("UnescapeStringLiteral_invalid")
|
||||||
|
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
|
||||||
|
<< _("const char *escaped = \"@\\xe3\\x81\";\n")
|
||||||
|
<< _("const char *escaped = \"\\xe3\\x81\";\n");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CppEditorPlugin::test_quickfix()
|
void CppEditorPlugin::test_quickfix()
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
#include <QTextCodec>
|
||||||
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|
||||||
@@ -118,6 +119,8 @@ void CppEditor::Internal::registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
|
|||||||
plugIn->addAutoReleasedObject(new InsertVirtualMethods);
|
plugIn->addAutoReleasedObject(new InsertVirtualMethods);
|
||||||
|
|
||||||
plugIn->addAutoReleasedObject(new OptimizeForLoop);
|
plugIn->addAutoReleasedObject(new OptimizeForLoop);
|
||||||
|
|
||||||
|
plugIn->addAutoReleasedObject(new EscapeStringLiteral);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the following anonymous namespace all functions are collected, which could be of interest for
|
// In the following anonymous namespace all functions are collected, which could be of interest for
|
||||||
@@ -4863,3 +4866,176 @@ void OptimizeForLoop::match(const CppQuickFixInterface &interface, QuickFixOpera
|
|||||||
result.append(QuickFixOperation::Ptr(op));
|
result.append(QuickFixOperation::Ptr(op));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class EscapeStringLiteralOperation: public CppQuickFixOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EscapeStringLiteralOperation(const CppQuickFixInterface &interface,
|
||||||
|
ExpressionAST *literal, bool escape)
|
||||||
|
: CppQuickFixOperation(interface)
|
||||||
|
, m_literal(literal)
|
||||||
|
, m_escape(escape)
|
||||||
|
{
|
||||||
|
if (m_escape) {
|
||||||
|
setDescription(QApplication::translate("CppTools::QuickFix",
|
||||||
|
"Escape String Literal as UTF-8"));
|
||||||
|
} else {
|
||||||
|
setDescription(QApplication::translate("CppTools::QuickFix",
|
||||||
|
"Unescape String Literal as UTF-8"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static inline bool isDigit(quint8 ch, int base)
|
||||||
|
{
|
||||||
|
if (base == 8)
|
||||||
|
return ch >= '0' && ch < '8';
|
||||||
|
if (base == 16)
|
||||||
|
return isxdigit(ch);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray escapeString(const QByteArray &contents)
|
||||||
|
{
|
||||||
|
QByteArray newContents;
|
||||||
|
for (int i = 0; i < contents.length(); ++i) {
|
||||||
|
quint8 c = contents.at(i);
|
||||||
|
if (isascii(c) && isprint(c)) {
|
||||||
|
newContents += c;
|
||||||
|
} else {
|
||||||
|
newContents += QByteArray("\\x") +
|
||||||
|
QByteArray::number(c, 16).rightJustified(2, '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray unescapeString(const QByteArray &contents)
|
||||||
|
{
|
||||||
|
QByteArray newContents;
|
||||||
|
const int len = contents.length();
|
||||||
|
for (int i = 0; i < len; ++i) {
|
||||||
|
quint8 c = contents.at(i);
|
||||||
|
if (c == '\\' && i < len - 1) {
|
||||||
|
int idx = i + 1;
|
||||||
|
quint8 ch = contents.at(idx);
|
||||||
|
int base = 0;
|
||||||
|
int maxlen = 0;
|
||||||
|
if (isDigit(ch, 8)) {
|
||||||
|
base = 8;
|
||||||
|
maxlen = 3;
|
||||||
|
} else if ((ch == 'x' || ch == 'X') && idx < len - 1) {
|
||||||
|
base = 16;
|
||||||
|
maxlen = 2;
|
||||||
|
ch = contents.at(++idx);
|
||||||
|
}
|
||||||
|
if (base > 0) {
|
||||||
|
QByteArray buf;
|
||||||
|
while (isDigit(ch, base) && idx < len && buf.length() < maxlen) {
|
||||||
|
buf += ch;
|
||||||
|
++idx;
|
||||||
|
if (idx == len)
|
||||||
|
break;
|
||||||
|
ch = contents.at(idx);
|
||||||
|
}
|
||||||
|
if (!buf.isEmpty()) {
|
||||||
|
bool ok;
|
||||||
|
uint value = buf.toUInt(&ok, base);
|
||||||
|
// Don't unescape isascii() && !isprint()
|
||||||
|
if (ok && (!isascii(value) || isprint(value))) {
|
||||||
|
newContents += value;
|
||||||
|
i = idx - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newContents += c;
|
||||||
|
c = contents.at(++i);
|
||||||
|
}
|
||||||
|
newContents += c;
|
||||||
|
}
|
||||||
|
return newContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuickFixOperation interface
|
||||||
|
public:
|
||||||
|
void perform()
|
||||||
|
{
|
||||||
|
CppRefactoringChanges refactoring(snapshot());
|
||||||
|
CppRefactoringFilePtr currentFile = refactoring.file(fileName());
|
||||||
|
|
||||||
|
const int startPos = currentFile->startOf(m_literal);
|
||||||
|
const int endPos = currentFile->endOf(m_literal);
|
||||||
|
|
||||||
|
StringLiteralAST *stringLiteral = m_literal->asStringLiteral();
|
||||||
|
QTC_ASSERT(stringLiteral, return);
|
||||||
|
const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).
|
||||||
|
identifier->chars());
|
||||||
|
QByteArray newContents;
|
||||||
|
if (m_escape)
|
||||||
|
newContents = escapeString(oldContents);
|
||||||
|
else
|
||||||
|
newContents = unescapeString(oldContents);
|
||||||
|
|
||||||
|
if (oldContents != newContents) {
|
||||||
|
// Check UTF-8 byte array is correct or not.
|
||||||
|
QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8");
|
||||||
|
QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder());
|
||||||
|
const QString str = decoder->toUnicode(newContents);
|
||||||
|
const QByteArray utf8buf = str.toUtf8();
|
||||||
|
if (utf8codec->canEncode(str) && newContents == utf8buf) {
|
||||||
|
ChangeSet changes;
|
||||||
|
changes.replace(startPos + 1, endPos - 1, str);
|
||||||
|
currentFile->setChangeSet(changes);
|
||||||
|
currentFile->apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ExpressionAST *m_literal;
|
||||||
|
bool m_escape;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
void EscapeStringLiteral::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
|
||||||
|
{
|
||||||
|
const QList<AST *> &path = interface->path();
|
||||||
|
|
||||||
|
AST * const lastAst = path.last();
|
||||||
|
ExpressionAST *literal = lastAst->asStringLiteral();
|
||||||
|
if (!literal)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StringLiteralAST *stringLiteral = literal->asStringLiteral();
|
||||||
|
CppRefactoringFilePtr file = interface->currentFile();
|
||||||
|
const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars());
|
||||||
|
|
||||||
|
bool canEscape = false;
|
||||||
|
bool canUnescape = false;
|
||||||
|
for (int i = 0; i < contents.length(); ++i) {
|
||||||
|
quint8 c = contents.at(i);
|
||||||
|
if (!isascii(c) || !isprint(c)) {
|
||||||
|
canEscape = true;
|
||||||
|
} else if (c == '\\' && i < contents.length() - 1) {
|
||||||
|
c = contents.at(++i);
|
||||||
|
if ((c >= '0' && c < '8') || c == 'x' || c == 'X')
|
||||||
|
canUnescape = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canEscape) {
|
||||||
|
QuickFixOperation::Ptr op(
|
||||||
|
new EscapeStringLiteralOperation(interface, literal, true));
|
||||||
|
result.append(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canUnescape) {
|
||||||
|
QuickFixOperation::Ptr op(
|
||||||
|
new EscapeStringLiteralOperation(interface, literal, false));
|
||||||
|
result.append(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -524,6 +524,19 @@ public:
|
|||||||
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
|
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Escapes or unescapes a string literal as UTF-8.
|
||||||
|
|
||||||
|
Escapes non-ASCII characters in a string literal to hexadecimal escape sequences.
|
||||||
|
Unescapes octal or hexadecimal escape sequences in a string literal.
|
||||||
|
String literals are handled as UTF-8 even if file's encoding is not UTF-8.
|
||||||
|
*/
|
||||||
|
class EscapeStringLiteral : public CppQuickFixFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace CppEditor
|
} // namespace CppEditor
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user