Snippets: Split error messages out of the parsed snippet

Change-Id: Ic5d6664c86405c558e8bb133b854521e4eaef58a
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2021-04-20 14:25:18 +02:00
parent d9862a767b
commit c8f8513c8d
3 changed files with 68 additions and 40 deletions

View File

@@ -70,6 +70,22 @@ public:
} }
}; };
QString SnippetParseError::htmlMessage() const
{
QString message = errorMessage;
if (pos < 0 || pos > 50)
return message;
QString detail = text.left(50);
if (detail != text)
detail.append("...");
detail.replace(QChar::Space, "&nbsp;");
message.append("<br><code>" + detail + "<br>");
for (int i = 0; i < pos; ++i)
message.append("&nbsp;");
message.append("^</code>");
return message;
}
// -------------------------------------------------------------------- // --------------------------------------------------------------------
// Snippet: // Snippet:
// -------------------------------------------------------------------- // --------------------------------------------------------------------
@@ -189,7 +205,12 @@ QString Snippet::generateTip() const
{'<', "&lt;"}, {'<', "&lt;"},
{'>', "&gt;"}}; {'>', "&gt;"}};
ParsedSnippet parsedSnippet = Snippet::parse(m_content); SnippetParseResult result = Snippet::parse(m_content);
if (Utils::holds_alternative<SnippetParseError>(result))
return Utils::get<SnippetParseError>(result).htmlMessage();
QTC_ASSERT(Utils::holds_alternative<ParsedSnippet>(result), return {});
auto parsedSnippet = Utils::get<ParsedSnippet>(result);
QString tip("<nobr>"); QString tip("<nobr>");
int pos = 0; int pos = 0;
@@ -215,7 +236,7 @@ QString Snippet::generateTip() const
return tip; return tip;
} }
ParsedSnippet Snippet::parse(const QString &snippet) SnippetParseResult Snippet::parse(const QString &snippet)
{ {
static UppercaseMangler ucMangler; static UppercaseMangler ucMangler;
static LowercaseMangler lcMangler; static LowercaseMangler lcMangler;
@@ -228,15 +249,10 @@ ParsedSnippet Snippet::parse(const QString &snippet)
= Utils::TemplateEngine::processText(Utils::globalMacroExpander(), snippet, = Utils::TemplateEngine::processText(Utils::globalMacroExpander(), snippet,
&errorMessage); &errorMessage);
result.success = errorMessage.isEmpty(); if (!errorMessage.isEmpty())
if (!result.success) { return {SnippetParseError{errorMessage, {}, -1}};
result.text = snippet;
result.errorMessage = errorMessage;
return result;
}
const int count = preprocessedSnippet.count(); const int count = preprocessedSnippet.count();
bool success = true;
int start = -1; int start = -1;
NameMangler *mangler = nullptr; NameMangler *mangler = nullptr;
@@ -260,8 +276,9 @@ ParsedSnippet Snippet::parse(const QString &snippet)
} }
if (mangler) { if (mangler) {
success = false; return SnippetParseResult{SnippetParseError{tr("Expected delimiter after mangler id"),
break; preprocessedSnippet,
i}};
} }
if (current == QLatin1Char(':') && start >= 0) { if (current == QLatin1Char(':') && start >= 0) {
@@ -272,8 +289,11 @@ ParsedSnippet Snippet::parse(const QString &snippet)
} else if (next == QLatin1Char('c')) { } else if (next == QLatin1Char('c')) {
mangler = &tcMangler; mangler = &tcMangler;
} else { } else {
success = false; return SnippetParseResult{
break; SnippetParseError{tr("Expected mangler id 'l'(lowercase), 'u'(uppercase), "
"or 'c'(titlecase) after colon"),
preprocessedSnippet,
i}};
} }
++i; ++i;
continue; continue;
@@ -288,17 +308,12 @@ ParsedSnippet Snippet::parse(const QString &snippet)
result.text.append(current); result.text.append(current);
} }
if (start >= 0) if (start >= 0) {
success = false; return SnippetParseResult{
SnippetParseError{tr("Missing closing variable delimiter"), result.text, start}};
result.success = success;
if (!success) {
result.ranges.clear();
result.text = preprocessedSnippet;
} }
return result; return SnippetParseResult(result);
} }
#ifdef WITH_TESTS #ifdef WITH_TESTS
@@ -418,17 +433,19 @@ void Internal::TextEditorPlugin::testSnippetParsing()
Q_ASSERT(ranges_start.count() == ranges_length.count()); // sanity check for the test data Q_ASSERT(ranges_start.count() == ranges_length.count()); // sanity check for the test data
Q_ASSERT(ranges_start.count() == ranges_mangler.count()); // sanity check for the test data Q_ASSERT(ranges_start.count() == ranges_mangler.count()); // sanity check for the test data
ParsedSnippet result = Snippet::parse(input); SnippetParseResult result = Snippet::parse(input);
QCOMPARE(Utils::holds_alternative<ParsedSnippet>(result), success);
QCOMPARE(result.text, text); ParsedSnippet snippet = Utils::get<ParsedSnippet>(result);
QCOMPARE(result.success, success);
QCOMPARE(result.ranges.count(), ranges_start.count()); QCOMPARE(snippet.text, text);
QCOMPARE(snippet.ranges.count(), ranges_start.count());
for (int i = 0; i < ranges_start.count(); ++i) { for (int i = 0; i < ranges_start.count(); ++i) {
QCOMPARE(result.ranges.at(i).start, ranges_start.at(i)); QCOMPARE(snippet.ranges.at(i).start, ranges_start.at(i));
QCOMPARE(result.ranges.at(i).length, ranges_length.at(i)); QCOMPARE(snippet.ranges.at(i).length, ranges_length.at(i));
Utils::Id id = NOMANGLER_ID; Utils::Id id = NOMANGLER_ID;
if (result.ranges.at(i).mangler) if (snippet.ranges.at(i).mangler)
id = result.ranges.at(i).mangler->id(); id = snippet.ranges.at(i).mangler->id();
QCOMPARE(id, ranges_mangler.at(i)); QCOMPARE(id, ranges_mangler.at(i));
} }
} }

View File

@@ -28,6 +28,7 @@
#include <texteditor/texteditor_global.h> #include <texteditor/texteditor_global.h>
#include <utils/id.h> #include <utils/id.h>
#include <utils/variant.h>
#include <QChar> #include <QChar>
#include <QList> #include <QList>
@@ -48,8 +49,6 @@ class TEXTEDITOR_EXPORT ParsedSnippet
{ {
public: public:
QString text; QString text;
QString errorMessage;
bool success;
struct Range { struct Range {
Range(int s, int l, NameMangler *m) : start(s), length(l), mangler(m) { } Range(int s, int l, NameMangler *m) : start(s), length(l), mangler(m) { }
int start; int start;
@@ -59,8 +58,21 @@ public:
QList<Range> ranges; QList<Range> ranges;
}; };
class TEXTEDITOR_EXPORT SnippetParseError
{
public:
QString errorMessage;
QString text;
int pos;
QString htmlMessage() const;
};
using SnippetParseResult = Utils::variant<ParsedSnippet, SnippetParseError>;
class TEXTEDITOR_EXPORT Snippet class TEXTEDITOR_EXPORT Snippet
{ {
Q_DECLARE_TR_FUNCTIONS(Snippet)
public: public:
explicit Snippet(const QString &groupId = QString(), const QString &id = QString()); explicit Snippet(const QString &groupId = QString(), const QString &id = QString());
~Snippet(); ~Snippet();
@@ -91,7 +103,7 @@ public:
static const QChar kVariableDelimiter; static const QChar kVariableDelimiter;
static const QChar kEscapeChar; static const QChar kEscapeChar;
static ParsedSnippet parse(const QString &snippet); static SnippetParseResult parse(const QString &snippet);
private: private:
bool m_isRemoved = false; bool m_isRemoved = false;

View File

@@ -2700,15 +2700,14 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
void TextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet) void TextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet)
{ {
ParsedSnippet data = Snippet::parse(snippet); SnippetParseResult result = Snippet::parse(snippet);
if (Utils::holds_alternative<SnippetParseError>(result)) {
if (!data.success) { const auto &error = Utils::get<SnippetParseError>(result);
QString message = QString::fromLatin1("Cannot parse snippet \"%1\".").arg(snippet); QMessageBox::warning(this, QLatin1String("Snippet Parse Error"), error.htmlMessage());
if (!data.errorMessage.isEmpty())
message += QLatin1String("\nParse error: ") + data.errorMessage;
QMessageBox::warning(this, QLatin1String("Snippet Parse Error"), message);
return; return;
} }
QTC_ASSERT(Utils::holds_alternative<ParsedSnippet>(result), return);
ParsedSnippet data = Utils::get<ParsedSnippet>(result);
QTextCursor cursor = cursor_arg; QTextCursor cursor = cursor_arg;
cursor.beginEditBlock(); cursor.beginEditBlock();