Added paste lang option for pasting programming code.
Expects you to paste only string literals with possible concatenation operators like . or +
This commit is contained in:
parent
35d1e75d35
commit
fbd630489e
8 changed files with 302 additions and 2 deletions
|
|
@ -141,14 +141,14 @@ void DatabaseWindow::createActions()
|
||||||
icon.addFile(QString::fromUtf8(":/icons/token_shortland_character.png"), QSize(), QIcon::Normal, QIcon::On);
|
icon.addFile(QString::fromUtf8(":/icons/token_shortland_character.png"), QSize(), QIcon::Normal, QIcon::On);
|
||||||
auto action = actionCopyAsCString = new QAction(icon, tr("Copy as C string"), this);
|
auto action = actionCopyAsCString = new QAction(icon, tr("Copy as C string"), this);
|
||||||
action->setObjectName("actionCopyAsCString");
|
action->setObjectName("actionCopyAsCString");
|
||||||
action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_C));
|
action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
QIcon icon;
|
QIcon icon;
|
||||||
icon.addFile(QString::fromUtf8(":/icons/token_shortland_character.png"), QSize(), QIcon::Normal, QIcon::On);
|
icon.addFile(QString::fromUtf8(":/icons/token_shortland_character.png"), QSize(), QIcon::Normal, QIcon::On);
|
||||||
auto action = actionCopyAsRawCppString = new QAction(icon, tr("Copy as raw C++-string"), this);
|
auto action = actionCopyAsRawCppString = new QAction(icon, tr("Copy as raw C++-string"), this);
|
||||||
action->setObjectName("actionCopyAsRawCppString");
|
action->setObjectName("actionCopyAsRawCppString");
|
||||||
action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_C));
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
QIcon icon;
|
QIcon icon;
|
||||||
|
|
@ -213,6 +213,13 @@ void DatabaseWindow::createActions()
|
||||||
action->setObjectName("actionOpenSql");
|
action->setObjectName("actionOpenSql");
|
||||||
action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_O));
|
action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_O));
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
QIcon icon;
|
||||||
|
//icon.addFile(QString::fromUtf8(":/icons/folder.png"), QSize(), QIcon::Normal, QIcon::On);
|
||||||
|
auto action = actionPasteLangString = new QAction(icon, tr("Paste lang string"), this);
|
||||||
|
action->setObjectName("actionPasteLangString");
|
||||||
|
action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V));
|
||||||
|
}
|
||||||
{
|
{
|
||||||
QIcon icon;
|
QIcon icon;
|
||||||
icon.addFile(QString::fromUtf8(":/icons/script_save.png"), QSize(), QIcon::Normal, QIcon::On);
|
icon.addFile(QString::fromUtf8(":/icons/script_save.png"), QSize(), QIcon::Normal, QIcon::On);
|
||||||
|
|
@ -257,6 +264,8 @@ void DatabaseWindow::initMenus()
|
||||||
actionCopy,
|
actionCopy,
|
||||||
actionCopyAsCString,
|
actionCopyAsCString,
|
||||||
actionCopyAsRawCppString,
|
actionCopyAsRawCppString,
|
||||||
|
// standard Paste missing Ctrl+V works however by default
|
||||||
|
actionPasteLangString,
|
||||||
actionGenerateCode
|
actionGenerateCode
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -482,6 +491,14 @@ void DatabaseWindow::on_actionOpenSql_triggered()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DatabaseWindow::on_actionPasteLangString_triggered()
|
||||||
|
{
|
||||||
|
auto query_tool = GetActiveQueryTool();
|
||||||
|
if (query_tool) {
|
||||||
|
query_tool->pasteLangString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DatabaseWindow::on_actionSaveSql_triggered()
|
void DatabaseWindow::on_actionSaveSql_triggered()
|
||||||
{
|
{
|
||||||
auto query_tool = GetActiveQueryTool();
|
auto query_tool = GetActiveQueryTool();
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ private:
|
||||||
QAction *actionInspectUserSchemas = nullptr; ///< Create or switch to pgcatalog tab
|
QAction *actionInspectUserSchemas = nullptr; ///< Create or switch to pgcatalog tab
|
||||||
QAction *actionNewSql = nullptr;
|
QAction *actionNewSql = nullptr;
|
||||||
QAction *actionOpenSql = nullptr;
|
QAction *actionOpenSql = nullptr;
|
||||||
|
QAction *actionPasteLangString = nullptr;
|
||||||
QAction *actionSaveSql = nullptr;
|
QAction *actionSaveSql = nullptr;
|
||||||
QAction *actionSaveSqlAs = nullptr;
|
QAction *actionSaveSqlAs = nullptr;
|
||||||
QAction *actionSaveCopyOfSqlAs = nullptr;
|
QAction *actionSaveCopyOfSqlAs = nullptr;
|
||||||
|
|
@ -138,6 +139,7 @@ private slots:
|
||||||
void on_actionInspectUserSchemas_triggered();
|
void on_actionInspectUserSchemas_triggered();
|
||||||
void on_actionNewSql_triggered();
|
void on_actionNewSql_triggered();
|
||||||
void on_actionOpenSql_triggered();
|
void on_actionOpenSql_triggered();
|
||||||
|
void on_actionPasteLangString_triggered();
|
||||||
void on_actionSaveSql_triggered();
|
void on_actionSaveSql_triggered();
|
||||||
void on_actionSaveSqlAs_triggered();
|
void on_actionSaveSqlAs_triggered();
|
||||||
void on_actionSaveCopyOfSqlAs_triggered();
|
void on_actionSaveCopyOfSqlAs_triggered();
|
||||||
|
|
|
||||||
|
|
@ -559,6 +559,13 @@ void QueryTool::copyQueryAsRawCppString()
|
||||||
QApplication::clipboard()->setText(cs);
|
QApplication::clipboard()->setText(cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QueryTool::pasteLangString()
|
||||||
|
{
|
||||||
|
QString s = QApplication::clipboard()->text();
|
||||||
|
s = ConvertLangToSqlString(s);
|
||||||
|
ui->queryEdit->insertPlainText(s);
|
||||||
|
}
|
||||||
|
|
||||||
void QueryTool::generateCode()
|
void QueryTool::generateCode()
|
||||||
{
|
{
|
||||||
QString command = getCommand();
|
QString command = getCommand();
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ public slots:
|
||||||
void saveCopyAs();
|
void saveCopyAs();
|
||||||
void copyQueryAsCString();
|
void copyQueryAsCString();
|
||||||
void copyQueryAsRawCppString();
|
void copyQueryAsRawCppString();
|
||||||
|
void pasteLangString();
|
||||||
void cancel();
|
void cancel();
|
||||||
void exportData();
|
void exportData();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,3 +146,211 @@ QString ConvertToMultiLineRawCppString(const QString &in)
|
||||||
out.append("\n)" + delim + "\"");
|
out.append("\n)" + delim + "\"");
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ConvertLangToSqlString(const QString &in)
|
||||||
|
{
|
||||||
|
// Assume mostly C++ for now but allow some other things like
|
||||||
|
// - single quotes (php)
|
||||||
|
// - concattenation operators . (php) and + (java)
|
||||||
|
|
||||||
|
// Allow cpp prefixes L u8 u U
|
||||||
|
|
||||||
|
// Parser flow, we start in whitespace state and search for prefix|opening quote
|
||||||
|
// parse string and process escapes
|
||||||
|
// if escape is \r strip it if \n go to new line
|
||||||
|
// until we reach matching end quote
|
||||||
|
// skip whitespace and + or .
|
||||||
|
|
||||||
|
|
||||||
|
QString output;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
WHITESPACE,
|
||||||
|
PREFIX,
|
||||||
|
IN_STRING,
|
||||||
|
END,
|
||||||
|
ERROR
|
||||||
|
} state = WHITESPACE;
|
||||||
|
int index = 0;
|
||||||
|
QChar quote = '\0';
|
||||||
|
while (state != ERROR && state != END && index < in.length()) {
|
||||||
|
if (state == WHITESPACE) {
|
||||||
|
// skip all whitespace untill we encounter something else
|
||||||
|
// we also skip concatenation operators. Note this code is not trying to validate
|
||||||
|
// for correct syntax so it will quite happily accept many incorrect constructs
|
||||||
|
// that doesn't matter however as we are just trying to strip everything which is not SQL.
|
||||||
|
while (index < in.length() && (in[index].isSpace() || in[index] == '+' || in[index] == '.')) ++index;
|
||||||
|
if (index == in.length()) {
|
||||||
|
state == END;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Assume quotes can vary
|
||||||
|
if (in[index] == '\'' || in[index] == '\"') {
|
||||||
|
quote = in[index];
|
||||||
|
++index;
|
||||||
|
state = IN_STRING;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = PREFIX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (state == PREFIX) {
|
||||||
|
auto c = in[index];
|
||||||
|
if (c == 'L' || c == 'U') {
|
||||||
|
// C++ prefix expect C++ double quote
|
||||||
|
if (in.length() > index+ 1 && in[index+1] == '"') {
|
||||||
|
index += 2;
|
||||||
|
state = IN_STRING;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c == 'u') {
|
||||||
|
// C++ prefix expect C++ double quote
|
||||||
|
if (in.length() > index+ 2 && in[index+1] == '8' && in[index+2] == '"') {
|
||||||
|
index += 3;
|
||||||
|
state = IN_STRING;
|
||||||
|
}
|
||||||
|
else if (in.length() > index+ 1 && in[index+1] == '"') {
|
||||||
|
index += 2;
|
||||||
|
state = IN_STRING;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (state == IN_STRING) {
|
||||||
|
// scan contents of string and process any escapes encountered
|
||||||
|
bool escape = false;
|
||||||
|
while (state != ERROR && index < in.length()) {
|
||||||
|
QChar c = in[index];
|
||||||
|
if (escape) {
|
||||||
|
if (c == 'a') output += '\x07';
|
||||||
|
else if (c == 'a') output += '\x07';
|
||||||
|
else if (c == 'b') output += '\x08';
|
||||||
|
else if (c == 'f') output += '\x0c';
|
||||||
|
else if (c == 'n') output += '\n';
|
||||||
|
else if (c == 'r') ;
|
||||||
|
else if (c == 'v') ;
|
||||||
|
else if (c >= '0' && c <= '7') {
|
||||||
|
// process octal escape
|
||||||
|
if (in.length() > index + 2) {
|
||||||
|
char buf[4];
|
||||||
|
buf[0] = c.toLatin1();
|
||||||
|
buf[1] = in[++index].toLatin1();
|
||||||
|
buf[2] = in[++index].toLatin1();
|
||||||
|
buf[3] = 0;
|
||||||
|
long int v = strtol(buf, nullptr, 8);
|
||||||
|
if (v < 0x80) {
|
||||||
|
output += static_cast<QChar>(static_cast<char>(v));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c == 'x') {
|
||||||
|
// hex byte
|
||||||
|
if (in.length() > index + 2) {
|
||||||
|
char buf[3];
|
||||||
|
buf[0] = in[++index].toLatin1();
|
||||||
|
buf[1] = in[++index].toLatin1();
|
||||||
|
buf[2] = 0;
|
||||||
|
long int v = strtol(buf, nullptr, 16);
|
||||||
|
output += static_cast<QChar>(static_cast<char>(v));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c == 'u') {
|
||||||
|
// 4 digit hax unicode codepoint
|
||||||
|
// hex byte
|
||||||
|
if (in.length() > index + 4) {
|
||||||
|
char buf[5];
|
||||||
|
buf[0] = in[++index].toLatin1();
|
||||||
|
buf[1] = in[++index].toLatin1();
|
||||||
|
buf[2] = in[++index].toLatin1();
|
||||||
|
buf[3] = in[++index].toLatin1();
|
||||||
|
buf[4] = 0;
|
||||||
|
long int v = strtol(buf, nullptr, 16);
|
||||||
|
output += static_cast<QChar>(static_cast<ushort>(v));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (c == 'U') {
|
||||||
|
// 8 digit hax unicode codepoint
|
||||||
|
if (in.length() > index + 8) {
|
||||||
|
char buf[9];
|
||||||
|
buf[0] = in[++index].toLatin1();
|
||||||
|
buf[1] = in[++index].toLatin1();
|
||||||
|
buf[2] = in[++index].toLatin1();
|
||||||
|
buf[3] = in[++index].toLatin1();
|
||||||
|
buf[4] = in[++index].toLatin1();
|
||||||
|
buf[5] = in[++index].toLatin1();
|
||||||
|
buf[6] = in[++index].toLatin1();
|
||||||
|
buf[7] = in[++index].toLatin1();
|
||||||
|
buf[8] = 0;
|
||||||
|
uint v = static_cast<uint>(strtol(buf, nullptr, 16));
|
||||||
|
if (QChar::requiresSurrogates(v)) {
|
||||||
|
output += QChar(QChar::highSurrogate(v));
|
||||||
|
output += QChar(QChar::lowSurrogate(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
output += c;
|
||||||
|
}
|
||||||
|
escape = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (c == quote) {
|
||||||
|
state = WHITESPACE;
|
||||||
|
++index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Is there any language where string literals do not have to be terminated before the end of the line
|
||||||
|
// Not considering string literals that explicitly allow for multiline strings as these are often raw strings
|
||||||
|
// and can be copy pasted normally.
|
||||||
|
else if (c == '\n') {
|
||||||
|
state = WHITESPACE;
|
||||||
|
++index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (c == '\\') {
|
||||||
|
escape = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
output += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ QString msfloatToHumanReadableString(float ms);
|
||||||
void copySelectionToClipboard(const QTableView *view);
|
void copySelectionToClipboard(const QTableView *view);
|
||||||
QString ConvertToMultiLineCString(const QString &in);
|
QString ConvertToMultiLineCString(const QString &in);
|
||||||
QString ConvertToMultiLineRawCppString(const QString &in);
|
QString ConvertToMultiLineRawCppString(const QString &in);
|
||||||
|
QString ConvertLangToSqlString(const QString &in);
|
||||||
void exportTable(const QTableView *view, QTextStream &out);
|
void exportTable(const QTableView *view, QTextStream &out);
|
||||||
|
|
||||||
inline QString stdStrToQ(const std::string &s)
|
inline QString stdStrToQ(const std::string &s)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ QT += core widgets
|
||||||
HEADERS +=
|
HEADERS +=
|
||||||
|
|
||||||
SOURCES += main.cpp \
|
SOURCES += main.cpp \
|
||||||
|
tst_ConvertLangToSqlString.cpp \
|
||||||
tst_ExplainJsonParser.cpp \
|
tst_ExplainJsonParser.cpp \
|
||||||
tst_expected.cpp \
|
tst_expected.cpp \
|
||||||
tst_SqlLexer.cpp \
|
tst_SqlLexer.cpp \
|
||||||
|
|
|
||||||
63
tests/pglabtests/tst_ConvertLangToSqlString.cpp
Normal file
63
tests/pglabtests/tst_ConvertLangToSqlString.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock-matchers.h>
|
||||||
|
#include "util.h"
|
||||||
|
#include "PrintTo_Qt.h"
|
||||||
|
|
||||||
|
using namespace testing;
|
||||||
|
|
||||||
|
|
||||||
|
TEST(ConvertLangToSqlString, simple)
|
||||||
|
{
|
||||||
|
QString in(R"__( "SELECT" )__");
|
||||||
|
QString expected(R"__(SELECT)__");
|
||||||
|
|
||||||
|
auto output = ConvertLangToSqlString(in);
|
||||||
|
ASSERT_EQ(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ConvertLangToSqlString, testEscapedQuote)
|
||||||
|
{
|
||||||
|
QString in(R"__( "SELECT\"" )__");
|
||||||
|
QString expected(R"__(SELECT")__");
|
||||||
|
|
||||||
|
auto output = ConvertLangToSqlString(in);
|
||||||
|
ASSERT_EQ(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ConvertLangToSqlString, testEscapedNewLine)
|
||||||
|
{
|
||||||
|
QString in(R"__( "SELECT\nFROM" )__");
|
||||||
|
QString expected(R"__(SELECT
|
||||||
|
FROM)__");
|
||||||
|
|
||||||
|
auto output = ConvertLangToSqlString(in);
|
||||||
|
ASSERT_EQ(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ConvertLangToSqlString, testConcatPlus)
|
||||||
|
{
|
||||||
|
QString in(R"__( "SELECT" + " FROM" )__");
|
||||||
|
QString expected(R"__(SELECT FROM)__");
|
||||||
|
|
||||||
|
auto output = ConvertLangToSqlString(in);
|
||||||
|
ASSERT_EQ(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ConvertLangToSqlString, testConcatDot)
|
||||||
|
{
|
||||||
|
QString in(R"__( "SELECT"." FROM" )__");
|
||||||
|
QString expected(R"__(SELECT FROM)__");
|
||||||
|
|
||||||
|
auto output = ConvertLangToSqlString(in);
|
||||||
|
ASSERT_EQ(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ConvertLangToSqlString, testSemiColon)
|
||||||
|
{
|
||||||
|
QString in(R"__( "SELECT"." FROM"; )__");
|
||||||
|
QString expected(R"__(SELECT FROM)__");
|
||||||
|
|
||||||
|
auto output = ConvertLangToSqlString(in);
|
||||||
|
ASSERT_EQ(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue