diff --git a/src/backends/python/CMakeLists.txt b/src/backends/python/CMakeLists.txt --- a/src/backends/python/CMakeLists.txt +++ b/src/backends/python/CMakeLists.txt @@ -22,7 +22,6 @@ KF5::ConfigCore KF5::ConfigGui KF5::SyntaxHighlighting - Qt5::DBus ) install(TARGETS cantor_pythonbackend DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/backends/python/pythoncompletionobject.cpp b/src/backends/python/pythoncompletionobject.cpp --- a/src/backends/python/pythoncompletionobject.cpp +++ b/src/backends/python/pythoncompletionobject.cpp @@ -67,9 +67,7 @@ "print('|'.join(rlcompleter.Completer(__dict__).global_matches('%1')+rlcompleter.Completer(__dict__).attr_matches('%1')))" ).arg(command()); m_expression = session()->evaluateExpression(expr, Cantor::Expression::FinishingBehavior::DoNotDelete, true); - // TODO: Python exec the expression before connect, so manualy run handler. Uncomment the connection after removing DBus - // connect(m_expression, &Cantor::Expression::statusChanged, this, &PythonCompletionObject::extractCompletions); - extractCompletions(m_expression->status()); + connect(m_expression, &Cantor::Expression::statusChanged, this, &PythonCompletionObject::extractCompletions); } } @@ -97,9 +95,7 @@ const QString& expr = QString::fromLatin1("callable(%1)").arg(identifier()); m_expression = session()->evaluateExpression(expr, Cantor::Expression::FinishingBehavior::DoNotDelete, true); - // TODO: Python exec the expression before connect, so manualy run handler. Uncomment the connection after removing DBus - // connect(m_expression, &Cantor::Expression::statusChanged, this, &PythonCompletionObject::extractIdentifierType); - extractIdentifierType(m_expression->status()); + connect(m_expression, &Cantor::Expression::statusChanged, this, &PythonCompletionObject::extractIdentifierType); } } diff --git a/src/backends/python/pythonexpression.cpp b/src/backends/python/pythonexpression.cpp --- a/src/backends/python/pythonexpression.cpp +++ b/src/backends/python/pythonexpression.cpp @@ -52,9 +52,7 @@ m_tempFile = nullptr; } - PythonSession* pythonSession = static_cast(session()); - - pythonSession->runExpression(this); + session()->enqueueExpression(this); } QString PythonExpression::internalCommand() @@ -104,29 +102,47 @@ void PythonExpression::parseOutput(QString output) { - qDebug() << "output: " << output; + qDebug() << "expression output: " << output; if(command().simplified().startsWith(QLatin1String("help("))){ setResult(new Cantor::HelpResult(output.remove(output.lastIndexOf(QLatin1String("None")), 4))); } else { if (!output.isEmpty()) - setResult(new Cantor::TextResult(output)); + addResult(new Cantor::TextResult(output)); } - setStatus(Cantor::Expression::Done); + if (m_tempFile == nullptr || result() != nullptr) // not plot expression + setStatus(Cantor::Expression::Done); } void PythonExpression::parseError(QString error) { - qDebug() << "error: " << error; + qDebug() << "expression error: " << error; setErrorMessage(error.replace(QLatin1String("\n"), QLatin1String("
"))); setStatus(Cantor::Expression::Error); } void PythonExpression::imageChanged() { - addResult(new Cantor::ImageResult(QUrl::fromLocalFile(m_tempFile->fileName()))); + if(m_tempFile->size() <= 0) + return; + + Cantor::ImageResult* newResult = new Cantor::ImageResult(QUrl::fromLocalFile(m_tempFile->fileName())); + if (result() == nullptr) + setResult(newResult); + else + { + bool found = false; + for (int i = 0; i < results().size(); i++) + if (results()[i]->type() == newResult->type()) + { + replaceResult(i, newResult); + found = true; + } + if (!found) + addResult(newResult); + } setStatus(Done); } diff --git a/src/backends/python/pythonserver.cpp b/src/backends/python/pythonserver.cpp --- a/src/backends/python/pythonserver.cpp +++ b/src/backends/python/pythonserver.cpp @@ -146,32 +146,21 @@ QString PythonServer::variables(bool parseValue) const { - // FIXME: This code allows get full form of numpy array, but for big arrays it's could cause performonce problems - // especially for displaying in variables panel - // So, uncomment this, when fix this problem - /* + PyRun_SimpleString( "try: \n" " import numpy \n" " __cantor_numpy_internal__ = numpy.get_printoptions()['threshold'] \n" " numpy.set_printoptions(threshold=100000000) \n" "except ModuleNotFoundError: \n" " pass \n" - - "try: \n" - " import numpy \n" - " numpy.set_printoptions(threshold=__cantor_numpy_internal__) \n" - " del __cantor_numpy_internal__ \n" - "except ModuleNotFoundError: \n" - " pass \n" - */ + ); PyRun_SimpleString("__tmp_globals__ = globals()"); PyObject* globals = PyObject_GetAttrString(m_pModule,"__tmp_globals__"); PyObject *key, *value; Py_ssize_t pos = 0; QStringList vars; - const QChar sep(30); // INFORMATION SEPARATOR TWO while (PyDict_Next(globals, &pos, &key, &value)) { const QString& keyString = pyObjectToQString(key); if (keyString.startsWith(QLatin1String("__"))) @@ -196,10 +185,19 @@ valueString = pyObjectToQString(PyObject_Repr(value)); - vars.append(keyString + QChar(31) + valueString); + vars.append(keyString + QChar(17) + valueString); } - return vars.join(sep); + PyRun_SimpleString( + "try: \n" + " import numpy \n" + " numpy.set_printoptions(threshold=__cantor_numpy_internal__) \n" + " del __cantor_numpy_internal__ \n" + "except ModuleNotFoundError: \n" + " pass \n" + ); + + return vars.join(QChar(18))+QChar(18); } diff --git a/src/backends/python/pythonservermain.cpp b/src/backends/python/pythonservermain.cpp new file mode 100644 --- /dev/null +++ b/src/backends/python/pythonservermain.cpp @@ -0,0 +1,124 @@ +/* + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + --- + Copyright (C) 2015 Minh Ngo + */ + +#include + +#include +#include +#include +#include + +#include "pythonserver.h" + +const QChar recordSep(30); +const QChar unitSep(31); +const char messageEnd = 29; + +PythonServer server; +QTimer inputTimer; +QMetaObject::Connection connection; + +QString inputBuffer; + +QLatin1String LOGIN("login"); +QLatin1String EXIT("exit"); +QLatin1String CODE("code"); +QLatin1String FILEPATH("setFilePath"); +QLatin1String MODEL("model"); + + +void routeInput() { + QByteArray bytes; + char c; + while (std::cin.get(c)) + { + if (messageEnd == c) + break; + else + bytes.append(c); + } + inputBuffer.append(QString::fromLocal8Bit(bytes)); + if (inputBuffer.isEmpty()) + return; + + const QStringList& records = inputBuffer.split(recordSep); + inputBuffer.clear(); + + if (records.size() == 2) + { + if (records[0] == EXIT) + { + QObject::disconnect(connection); + QObject::connect(&inputTimer, &QTimer::timeout, QCoreApplication::instance(), &QCoreApplication::quit); + } + else if (records[0] == LOGIN) + { + server.login(); + const QByteArray bytes = QString::fromLatin1("login done").toLocal8Bit(); + //std::cout << bytes.data(); + } + else if (records[0] == CODE) + { + server.runPythonCommand(records[1]); + + const QString& result = + server.getOutput() + + unitSep + + server.getError() + + QLatin1Char(messageEnd); + + const QByteArray bytes = result.toLocal8Bit(); + std::cout << bytes.data(); + } + else if (records[0] == FILEPATH) + { + server.setFilePath(records[1]); + } + else if (records[0] == MODEL) + { + bool ok; + bool val = records[1].toInt(&ok); + + QString result; + if (ok) + result = server.variables(val) + unitSep; + else + result = unitSep + QLatin1String("Invalid argument %1 for 'model' command", val); + result += QLatin1Char(messageEnd); + + const QByteArray bytes = result.toLocal8Bit(); + std::cout << bytes.data(); + } + std::cout.flush(); + } +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + connection = QObject::connect(&inputTimer, &QTimer::timeout, routeInput); + inputTimer.setInterval(100); + inputTimer.start(); + + std::cout << "ready" << std::endl; + + return app.exec(); +} diff --git a/src/backends/python/pythonsession.h b/src/backends/python/pythonsession.h --- a/src/backends/python/pythonsession.h +++ b/src/backends/python/pythonsession.h @@ -26,78 +26,43 @@ #include #include -namespace Cantor { - class DefaultVariableModel; -} - -class PythonExpression; -class PythonVariableModel; -class KDirWatch; -class QDBusInterface; -class KProcess; +class QProcess; class CANTOR_PYTHONBACKEND_EXPORT PythonSession : public Cantor::Session { Q_OBJECT public: - PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName, const QString DbusChannelName); - ~PythonSession() override = default; + PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName); + ~PythonSession() override; void login() override; void logout() override; void interrupt() override; - void runExpression(PythonExpression* expr); Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; Cantor::CompletionObject* completionFor(const QString& command, int index=-1) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; - Cantor::DefaultVariableModel* variableModel() const override; void setWorksheetPath(const QString& path) override; virtual bool integratePlots() const = 0; virtual QStringList autorunScripts() const = 0; virtual bool variableManagement() const = 0; private: - PythonVariableModel* m_variableModel; - - QList m_runningExpressions; - PythonExpression* m_currentExpression; - - QDBusInterface* m_pIface; - KProcess* m_pProcess; + QProcess* m_process; QString serverName; - QString DbusChannelName; QString worksheetPath; int m_pythonVersion; - bool m_needUpdate; - - protected: QString m_output; - QString m_error; private: - void getPythonCommandOutput(const QString& commandProcessing); - - QString identifyPythonModule(const QString& command) const; - QString identifyVariableModule(const QString& command) const; - bool identifyKeywords(const QString& command); - - void runPythonCommand(const QString& command) const; - QString getOutput() const; - QString getError() const; - - virtual void readExpressionOutput(const QString& commandProcessing); - - protected: - void updateOutput(); + void runFirstExpression() override; + void readOutput(); - private Q_SLOTS: - void readOutput(const QString& commandProcessing); - void expressionFinished(); + void sendCommand(const QString& command, const QStringList arguments = QStringList()) const; }; #endif /* _PYTHONSESSION_H */ diff --git a/src/backends/python/pythonsession.cpp b/src/backends/python/pythonsession.cpp --- a/src/backends/python/pythonsession.cpp +++ b/src/backends/python/pythonsession.cpp @@ -19,6 +19,7 @@ Copyright (C) 2015 Minh Ngo */ +#include #include "pythonsession.h" #include "pythonexpression.h" #include "pythonvariablemodel.h" @@ -29,120 +30,125 @@ #include #include - #include -#include -#include -#include - -#include +#include #include -#include +#ifndef Q_OS_WIN +#include +#endif -PythonSession::PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName, const QString DbusChannelName) + +const QChar recordSep(30); +const QChar unitSep(31); +const QChar messageEnd = 29; + +PythonSession::PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName) : Session(backend) - , m_variableModel(new PythonVariableModel(this)) - , m_currentExpression(nullptr) - , m_pIface(nullptr) - , m_pProcess(nullptr) + , m_process(nullptr) , serverName(serverName) - , DbusChannelName(DbusChannelName) , m_pythonVersion(pythonVersion) - , m_needUpdate(false) { + setVariableModel(new PythonVariableModel(this)); +} + +PythonSession::~PythonSession() +{ + if (m_process) { + m_process->kill(); + m_process->deleteLater(); + } } void PythonSession::login() { qDebug()<<"login"; emit loginStarted(); - // TODO: T6113, T6114 - if (m_pProcess) - m_pProcess->deleteLater(); - - m_pProcess = new KProcess(this); - m_pProcess->setOutputChannelMode(KProcess::SeparateChannels); + if (m_process) + m_process->deleteLater(); - (*m_pProcess) << QStandardPaths::findExecutable(serverName); + m_process = new QProcess(this); + m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel); - m_pProcess->start(); + m_process->start(QStandardPaths::findExecutable(serverName)); - m_pProcess->waitForStarted(); - m_pProcess->waitForReadyRead(); - QTextStream stream(m_pProcess->readAllStandardOutput()); + m_process->waitForStarted(); + m_process->waitForReadyRead(); + QTextStream stream(m_process->readAllStandardOutput()); const QString& readyStatus = QString::fromLatin1("ready"); - while (m_pProcess->state() == QProcess::Running) + while (m_process->state() == QProcess::Running) { const QString& rl = stream.readLine(); if (rl == readyStatus) break; } - if (!QDBusConnection::sessionBus().isConnected()) - { - qWarning() << "Can't connect to the D-Bus session bus.\n" - "To start it, run: eval `dbus-launch --auto-syntax`"; - return; - } - - const QString& serviceName = DbusChannelName + QString::fromLatin1("-%1").arg(m_pProcess->pid()); + connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonSession::readOutput); - m_pIface = new QDBusInterface(serviceName, - QString::fromLatin1("/"), QString(), QDBusConnection::sessionBus()); - if (!m_pIface->isValid()) - { - qWarning() << QDBusConnection::sessionBus().lastError().message(); - return; - } - - m_variableModel->setPythonServer(m_pIface); - - m_pIface->call(QString::fromLatin1("login")); - m_pIface->call(QString::fromLatin1("setFilePath"), worksheetPath); + sendCommand(QLatin1String("login")); + sendCommand(QLatin1String("setFilePath"), QStringList(worksheetPath)); const QStringList& scripts = autorunScripts(); if(!scripts.isEmpty()){ QString autorunScripts = scripts.join(QLatin1String("\n")); - getPythonCommandOutput(autorunScripts); - m_variableModel->update(); + evaluateExpression(autorunScripts, Cantor::Expression::DeleteOnFinish, true); + variableModel()->update(); } const QString& importerFile = QLatin1String(":py/import_default_modules.py"); evaluateExpression(fromSource(importerFile), Cantor::Expression::DeleteOnFinish, true); + changeStatus(Session::Done); emit loginDone(); } void PythonSession::logout() { - // TODO: T6113, T6114 - m_pProcess->terminate(); + if (!m_process) + return; + + sendCommand(QLatin1String("exit")); + m_process = nullptr; - m_variableModel->clearVariables(); + variableModel()->clearVariables(); qDebug()<<"logout"; changeStatus(Status::Disable); } void PythonSession::interrupt() { - // TODO: T6113, T6114 - if (m_pProcess->pid()) - m_pProcess->kill(); - - qDebug()<<"interrupt"; - - foreach(Cantor::Expression* e, m_runningExpressions) - e->interrupt(); + if(!expressionQueue().isEmpty()) + { + qDebug()<<"interrupting " << expressionQueue().first()->command(); + if(m_process->state() != QProcess::NotRunning) + { +#ifndef Q_OS_WIN + const int pid=m_process->pid(); + kill(pid, SIGINT); +#else + ; //TODO: interrupt the process on windows +#endif + } + for (Cantor::Expression* expression : expressionQueue()) + expression->setStatus(Cantor::Expression::Interrupted); + expressionQueue().clear(); + + // Cleanup inner state and call octave prompt printing + // If we move this code for interruption to Session, we need add function for + // cleaning before setting Done status + m_output.clear(); + m_process->write("\n"); + + qDebug()<<"done interrupting"; + } - m_runningExpressions.clear(); changeStatus(Cantor::Session::Done); } @@ -160,199 +166,71 @@ return expr; } -void PythonSession::runExpression(PythonExpression* expr) -{ - qDebug() << "run expression"; - - m_currentExpression = expr; - - const QString& command = expr->internalCommand(); - - readExpressionOutput(command); -} - -// Is called asynchronously in the Python3 plugin -void PythonSession::readExpressionOutput(const QString& commandProcessing) -{ - readOutput(commandProcessing); -} - -void PythonSession::getPythonCommandOutput(const QString& commandProcessing) -{ - runPythonCommand(commandProcessing); - - m_output = getOutput(); - m_error = getError(); -} - -bool PythonSession::identifyKeywords(const QString& command) -{ - QString verifyErrorImport; - - QString listKeywords; - QString keywordsString; - - QString moduleImported; - QString moduleVariable; - - getPythonCommandOutput(command); - - qDebug() << "verifyErrorImport: "; - - if(!m_error.isEmpty()){ - - qDebug() << "returned false"; - - return false; - } - - moduleImported += identifyPythonModule(command); - moduleVariable += identifyVariableModule(command); - - if((moduleVariable.isEmpty()) && (!command.endsWith(QLatin1String("*")))){ - keywordsString = command.section(QLatin1String(" "), 3).remove(QLatin1String(" ")); - } - - if(moduleVariable.isEmpty() && (command.endsWith(QLatin1String("*")))){ - listKeywords += QString::fromLatin1("import %1\n" \ - "print(dir(%1))\n" \ - "del %1\n").arg(moduleImported); - } - - if(!moduleVariable.isEmpty()){ - listKeywords += QLatin1String("print(dir(") + moduleVariable + QLatin1String("))\n"); - } - - if(!listKeywords.isEmpty()){ - getPythonCommandOutput(listKeywords); - - keywordsString = m_output; - - keywordsString.remove(QLatin1String("'")); - keywordsString.remove(QLatin1String(" ")); - keywordsString.remove(QLatin1String("[")); - keywordsString.remove(QLatin1String("]")); - } - - QStringList keywordsList = keywordsString.split(QLatin1String(",")); - PythonKeywords::instance()->loadFromModule(moduleVariable, keywordsList); - - qDebug() << "Module imported" << moduleImported; - - return true; -} - -QString PythonSession::identifyPythonModule(const QString& command) const -{ - QString module; - - if(command.contains(QLatin1String("import "))){ - module = command.section(QLatin1String(" "), 1, 1); - } - - qDebug() << "module identified" << module; - return module; -} - -QString PythonSession::identifyVariableModule(const QString& command) const +QSyntaxHighlighter* PythonSession::syntaxHighlighter(QObject* parent) { - QString variable; - - if(command.contains(QLatin1String("import "))){ - variable = command.section(QLatin1String(" "), 1, 1); - } - - if((command.contains(QLatin1String("import "))) && (command.contains(QLatin1String(" as ")))){ - variable = command.section(QLatin1String(" "), 3, 3); - } - - if(command.contains(QLatin1String("from "))){ - variable = QLatin1String(""); - } - - qDebug() << "variable identified" << variable; - return variable; + return new PythonHighlighter(parent, this, m_pythonVersion); } -void PythonSession::expressionFinished() +Cantor::CompletionObject* PythonSession::completionFor(const QString& command, int index) { - qDebug()<< "finished"; - PythonExpression* expression = qobject_cast(sender()); - - m_runningExpressions.removeAll(expression); - qDebug() << "size: " << m_runningExpressions.size(); + return new PythonCompletionObject(command, index, this); } -void PythonSession::updateOutput() +void PythonSession::runFirstExpression() { - m_needUpdate |= !m_currentExpression->isInternal(); - if(m_error.isEmpty()){ - m_currentExpression->parseOutput(m_output); - - qDebug() << "output: " << m_output; - } else { - m_currentExpression->parseError(m_error); + if (expressionQueue().isEmpty()) + return; - qDebug() << "error: " << m_error; - } + Cantor::Expression* expr = expressionQueue().first(); + const QString command = expr->internalCommand(); + qDebug() << "run first expression" << command; + expr->setStatus(Cantor::Expression::Computing); - if (m_needUpdate) + if (expr->isInternal() && command.startsWith(QLatin1String("%variables "))) { - m_variableModel->update(); - m_needUpdate = false; + const QString arg = command.section(QLatin1String(" "), 1); + sendCommand(QLatin1String("model"), QStringList(arg)); } - - changeStatus(Cantor::Session::Done); + else + sendCommand(QLatin1String("code"), QStringList(expr->internalCommand())); } -void PythonSession::readOutput(const QString& commandProcessing) +void PythonSession::sendCommand(const QString& command, const QStringList arguments) const { - qDebug() << "readOutput"; - - getPythonCommandOutput(commandProcessing); - - updateOutput(); + qDebug() << "send command: " << command << arguments; + const QString& message = command + recordSep + arguments.join(unitSep) + messageEnd; + m_process->write(message.toLocal8Bit()); } -QSyntaxHighlighter* PythonSession::syntaxHighlighter(QObject* parent) +void PythonSession::readOutput() { - return new PythonHighlighter(parent, this, m_pythonVersion); -} + while (m_process->bytesAvailable() > 0) + m_output.append(QString::fromLocal8Bit(m_process->readAll())); -Cantor::CompletionObject* PythonSession::completionFor(const QString& command, int index) -{ - return new PythonCompletionObject(command, index, this); -} + qDebug() << "m_output: " << m_output; -Cantor::DefaultVariableModel* PythonSession::variableModel() const -{ - return m_variableModel; -} - -void PythonSession::runPythonCommand(const QString& command) const -{ - // TODO: T6113, T6114 - m_pIface->call(QString::fromLatin1("runPythonCommand"), command); -} - -QString PythonSession::getOutput() const -{ - // TODO: T6113, T6114 - const QDBusReply& reply = m_pIface->call(QString::fromLatin1("getOutput")); - if (reply.isValid()) - return reply.value(); - - return reply.error().message(); -} + if (!m_output.contains(messageEnd)) + return; + const QStringList packages = m_output.split(messageEnd, QString::SkipEmptyParts); + if (m_output.endsWith(messageEnd)) + m_output.clear(); + else + m_output = m_output.section(messageEnd, -1); -QString PythonSession::getError() const -{ - // TODO: T6113, T6114 - const QDBusReply& reply = m_pIface->call(QString::fromLatin1("getError")); - if (reply.isValid()) - return reply.value(); + for (const QString& message: packages) + { + if (expressionQueue().isEmpty()) + break; - return reply.error().message(); + const QString& output = message.section(unitSep, 0, 0); + const QString& error = message.section(unitSep, 1, 1); + if(error.isEmpty()){ + static_cast(expressionQueue().first())->parseOutput(output); + } else { + static_cast(expressionQueue().first())->parseError(error); + } + finishFirstExpression(true); + } } void PythonSession::setWorksheetPath(const QString& path) diff --git a/src/backends/python/pythonvariablemodel.h b/src/backends/python/pythonvariablemodel.h --- a/src/backends/python/pythonvariablemodel.h +++ b/src/backends/python/pythonvariablemodel.h @@ -30,14 +30,15 @@ { public: PythonVariableModel( PythonSession* session); - ~PythonVariableModel() override = default; + ~PythonVariableModel() override; void update() override; - void setPythonServer(QDBusInterface* pIface); - private: - QDBusInterface* m_pIface; + Cantor::Expression* m_expression{nullptr}; + + private Q_SLOTS: + void extractVariables(Cantor::Expression::Status status); }; #endif /* _PYTHONVARIABLEMODEL_H */ diff --git a/src/backends/python/pythonvariablemodel.cpp b/src/backends/python/pythonvariablemodel.cpp --- a/src/backends/python/pythonvariablemodel.cpp +++ b/src/backends/python/pythonvariablemodel.cpp @@ -30,33 +30,64 @@ using namespace Cantor; PythonVariableModel::PythonVariableModel(PythonSession* session): - DefaultVariableModel(session), - m_pIface(nullptr) + DefaultVariableModel(session) { } -void PythonVariableModel::setPythonServer(QDBusInterface* pIface) +PythonVariableModel::~PythonVariableModel() { - m_pIface = pIface; + if (m_expression) + m_expression->setFinishingBehavior(Cantor::Expression::FinishingBehavior::DeleteOnFinish); } void PythonVariableModel::update() { - if (!m_pIface) + if (m_expression) return; - bool variableManagement = static_cast(session())->variableManagement(); - const QString& data = QDBusReply(m_pIface->call(QString::fromLatin1("variables"), variableManagement)).value(); - const QStringList& records = data.split(QChar(30), QString::SkipEmptyParts); + int variableManagement = static_cast(session())->variableManagement(); + const QString command = QString::fromLatin1("%variables %1").arg(variableManagement); + m_expression = session()->evaluateExpression(command, Cantor::Expression::FinishingBehavior::DoNotDelete, true); + connect(m_expression, &Cantor::Expression::statusChanged, this, &PythonVariableModel::extractVariables); +} - QList variables; - for (const QString& record : records) +void PythonVariableModel::extractVariables(Cantor::Expression::Status status) +{ + switch(status) { - const QString& name = record.section(QChar(31), 0, 0); - const QString& value = record.section(QChar(31), 1, 1); + case Cantor::Expression::Done: + { + Cantor::Result* result = m_expression->result(); + if (result) + { + const QString data = result->data().toString(); + // In Cantor server response DC2(18) is delimiter between variables + const QStringList& records = data.split(QChar(18), QString::SkipEmptyParts); + + QList variables; + for (const QString& record : records) + { + // DC1(17) is delimiter between variable name and its value. + const QString& name = record.section(QChar(17), 0, 0); + const QString& value = record.section(QChar(17), 1, 1); + + variables << Variable{name, value}; + } - variables << Variable{name, value}; + setVariables(variables); + } + break; + } + case Cantor::Expression::Interrupted: + case Cantor::Expression::Error: + { + qDebug() << "python variable model update finished with status" << (status == Cantor::Expression::Error? "Error" : "Interrupted"); + break; + } + default: + return; } - setVariables(variables); + m_expression->deleteLater(); + m_expression=nullptr; } diff --git a/src/backends/python2/CMakeLists.txt b/src/backends/python2/CMakeLists.txt --- a/src/backends/python2/CMakeLists.txt +++ b/src/backends/python2/CMakeLists.txt @@ -3,6 +3,11 @@ python2session.cpp ) +set(Python2Server_SRCS + ../python/pythonservermain.cpp + ../python/pythonserver.cpp +) + kconfig_add_kcfg_files(Python2Backend_SRCS settings.kcfgc) if(MSVC) @@ -12,13 +17,12 @@ include_directories(${PYTHON_LIBRARIES_DIR}) include_directories(${PYTHON_INCLUDE_DIR}) - add_backend(python2backend ${Python2Backend_SRCS}) +target_link_libraries(cantor_python2backend ${PYTHON_LIBRARIES} cantor_pythonbackend) -target_link_libraries(cantor_python2backend - ${PYTHON_LIBRARIES} - cantor_pythonbackend -) +add_executable(cantor_python2server ${Python2Server_SRCS}) +set_target_properties(cantor_python2server PROPERTIES INSTALL_RPATH_USE_LINK_PATH false) +target_link_libraries(cantor_python2server ${PYTHON_LIBRARIES} Qt5::Widgets) if(BUILD_TESTING) add_executable(testpython2 testpython2.cpp settings.cpp) @@ -29,4 +33,4 @@ install(FILES cantor_python2.knsrc DESTINATION ${KDE_INSTALL_CONFDIR}) install(FILES python2backend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) -add_subdirectory(python2server) +install(TARGETS cantor_python2server ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/backends/python2/python2server/CMakeLists.txt b/src/backends/python2/python2server/CMakeLists.txt deleted file mode 100644 --- a/src/backends/python2/python2server/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -include_directories(${PYTHON_INCLUDE_DIR}) - -set(Python2Server_SRCS - main.cpp - ../../python/pythonserver.cpp -) - -add_executable(cantor_python2server ${Python2Server_SRCS}) - -set_target_properties(cantor_python2server PROPERTIES INSTALL_RPATH_USE_LINK_PATH false) -target_link_libraries(cantor_python2server - ${PYTHON_LIBRARIES} - Qt5::Widgets - Qt5::DBus) - -install(TARGETS cantor_python2server ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/backends/python2/python2server/main.cpp b/src/backends/python2/python2server/main.cpp deleted file mode 100644 --- a/src/backends/python2/python2server/main.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. - - --- - Copyright (C) 2015 Minh Ngo - */ - -#include -#include -#include -#include -#include - -#include "../../python/pythonserver.h" - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - - if (!QDBusConnection::sessionBus().isConnected()) - { - qDebug() << "Can't connect to the D-Bus session bus.\n" - "To start it, run: eval `dbus-launch --auto-syntax`"; - return 1; - } - - const QString& serviceName = QStringLiteral("org.kde.Cantor.Python2-%1").arg(app.applicationPid()); - - if (!QDBusConnection::sessionBus().registerService(serviceName)) - { - qDebug() << QDBusConnection::sessionBus().lastError().message(); - return 2; - } - - PythonServer server; - QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), &server, QDBusConnection::ExportAllSlots); - - QTextStream(stdout) << "ready" << endl; - - return app.exec(); -} diff --git a/src/backends/python2/python2session.cpp b/src/backends/python2/python2session.cpp --- a/src/backends/python2/python2session.cpp +++ b/src/backends/python2/python2session.cpp @@ -31,7 +31,7 @@ #include Python2Session::Python2Session(Cantor::Backend* backend) - : PythonSession(backend, 2, QLatin1String("cantor_python2server"), QLatin1String("org.kde.Cantor.Python2")) + : PythonSession(backend, 2, QLatin1String("cantor_python2server")) { } diff --git a/src/backends/python2/testpython2.cpp b/src/backends/python2/testpython2.cpp --- a/src/backends/python2/testpython2.cpp +++ b/src/backends/python2/testpython2.cpp @@ -200,6 +200,8 @@ //the variable model shouldn't have any entries after the module imports QAbstractItemModel* model = session()->variableModel(); QVERIFY(model != nullptr); + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QVERIFY(model->rowCount() == 0); //create data for plotting @@ -210,16 +212,20 @@ QVERIFY(e != nullptr); QVERIFY(e->errorMessage().isEmpty() == true); + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); //the variable model should have two entries now QVERIFY(model->rowCount() == 2); //plot the data and check the results e = evalExp(QLatin1String( "plt.plot(t,s)\n" "plt.show()" )); - waitForSignal(e, SIGNAL(gotResult())); + QVERIFY(e != nullptr); + if (e->result() == nullptr) + waitForSignal(e, SIGNAL(gotResult())); QVERIFY(e->errorMessage().isEmpty() == true); QVERIFY(model->rowCount() == 2); //still only two variables @@ -254,6 +260,9 @@ void TestPython2::testCompletion() { + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + Cantor::CompletionObject* help = session()->completionFor(QLatin1String("ma"), 2); waitForSignal(help, SIGNAL(fetchingDone())); diff --git a/src/backends/python3/CMakeLists.txt b/src/backends/python3/CMakeLists.txt --- a/src/backends/python3/CMakeLists.txt +++ b/src/backends/python3/CMakeLists.txt @@ -3,15 +3,19 @@ python3session.cpp ) -kconfig_add_kcfg_files(Python3Backend_SRCS settings.kcfgc) +set(Python3Server_SRCS + ../python/pythonservermain.cpp + ../python/pythonserver.cpp +) +include_directories(${PYTHONLIBS3_INCLUDE_DIRS}) +kconfig_add_kcfg_files(Python3Backend_SRCS settings.kcfgc) add_backend(python3backend ${Python3Backend_SRCS}) +target_link_libraries(cantor_python3backend cantor_pythonbackend) -target_link_libraries(cantor_python3backend - cantor_pythonbackend - Qt5::DBus) - -add_subdirectory(python3server) +add_executable(cantor_python3server ${Python3Server_SRCS}) +set_target_properties(cantor_python3server PROPERTIES INSTALL_RPATH_USE_LINK_PATH false) +target_link_libraries(cantor_python3server ${PYTHONLIBS3_LIBRARIES} Qt5::Widgets) if(BUILD_TESTING) add_executable(testpython3 testpython3.cpp settings.cpp) @@ -27,3 +31,5 @@ install(FILES cantor_python3.knsrc DESTINATION ${KDE_INSTALL_CONFDIR}) install(FILES python3backend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) + +install(TARGETS cantor_python3server ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/backends/python3/python3server/CMakeLists.txt b/src/backends/python3/python3server/CMakeLists.txt deleted file mode 100644 --- a/src/backends/python3/python3server/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -include_directories(${PYTHONLIBS3_INCLUDE_DIRS}) - -set(Python3Server_SRCS - main.cpp - ../../python/pythonserver.cpp -) - -add_executable(cantor_python3server ${Python3Server_SRCS}) - -set_target_properties(cantor_python3server PROPERTIES INSTALL_RPATH_USE_LINK_PATH false) -target_link_libraries(cantor_python3server - ${PYTHONLIBS3_LIBRARIES} - Qt5::Widgets - Qt5::DBus) - -install(TARGETS cantor_python3server ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/backends/python3/python3server/main.cpp b/src/backends/python3/python3server/main.cpp deleted file mode 100644 --- a/src/backends/python3/python3server/main.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. - - --- - Copyright (C) 2015 Minh Ngo - */ - -#include -#include -#include -#include -#include - -#include "../../python/pythonserver.h" - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - - if (!QDBusConnection::sessionBus().isConnected()) - { - qDebug() << "Can't connect to the D-Bus session bus.\n" - "To start it, run: eval `dbus-launch --auto-syntax`"; - return 1; - } - - const QString& serviceName = QStringLiteral("org.kde.Cantor.Python3-%1").arg(app.applicationPid()); - - if (!QDBusConnection::sessionBus().registerService(serviceName)) - { - qDebug() << QDBusConnection::sessionBus().lastError().message(); - return 2; - } - - PythonServer server; - QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), &server, QDBusConnection::ExportAllSlots); - - QTextStream(stdout) << "ready" << endl; - - return app.exec(); -} diff --git a/src/backends/python3/python3session.cpp b/src/backends/python3/python3session.cpp --- a/src/backends/python3/python3session.cpp +++ b/src/backends/python3/python3session.cpp @@ -22,7 +22,7 @@ #include "settings.h" Python3Session::Python3Session(Cantor::Backend* backend) - : PythonSession(backend, 3, QLatin1String("cantor_python3server"), QLatin1String("org.kde.Cantor.Python3")) + : PythonSession(backend, 3, QLatin1String("cantor_python3server")) { } diff --git a/src/backends/python3/testpython3.cpp b/src/backends/python3/testpython3.cpp --- a/src/backends/python3/testpython3.cpp +++ b/src/backends/python3/testpython3.cpp @@ -111,6 +111,9 @@ void TestPython3::testCompletion() { + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + Cantor::CompletionObject* help = session()->completionFor(QLatin1String("p"), 1); waitForSignal(help, SIGNAL(fetchingDone())); @@ -197,16 +200,21 @@ QVERIFY(e != nullptr); QVERIFY(e->errorMessage().isEmpty() == true); + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); //the variable model should have two entries now QVERIFY(model->rowCount() == 2); //plot the data and check the results e = evalExp(QLatin1String( "plt.plot(t,s)\n" "plt.show()" )); - waitForSignal(e, SIGNAL(gotResult())); + QVERIFY(e != nullptr); + if (e->result() == nullptr) + waitForSignal(e, SIGNAL(gotResult())); + QVERIFY(e->errorMessage().isEmpty() == true); QVERIFY(model->rowCount() == 2); //still only two variables