diff --git a/src/backends/python/pythonsession.cpp b/src/backends/python/pythonsession.cpp index 5497a5ee..ef4a58c4 100644 --- a/src/backends/python/pythonsession.cpp +++ b/src/backends/python/pythonsession.cpp @@ -1,280 +1,280 @@ /* 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) 2012 Filipe Saraiva Copyright (C) 2015 Minh Ngo */ #include #include "pythonsession.h" #include "pythonexpression.h" #include "pythonvariablemodel.h" #include "pythonhighlighter.h" #include "pythoncompletionobject.h" #include "pythonkeywords.h" #include "pythonutils.h" #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #endif const QChar recordSep(30); const QChar unitSep(31); const QChar messageEnd = 29; -PythonSession::PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName) +PythonSession::PythonSession(Cantor::Backend* backend, int pythonVersion, const QUrl serverExecutableUrl) : Session(backend) , m_process(nullptr) - , serverName(serverName) + , m_serverExecutableUrl(serverExecutableUrl) , m_pythonVersion(pythonVersion) { setVariableModel(new PythonVariableModel(this)); } PythonSession::~PythonSession() { if (m_process) { disconnect(m_process, &QProcess::errorOccurred, this, &PythonSession::reportServerProcessError); m_process->kill(); m_process->deleteLater(); m_process = nullptr; } } void PythonSession::login() { qDebug()<<"login"; emit loginStarted(); if (m_process) m_process->deleteLater(); m_process = new QProcess(this); m_process->setProcessChannelMode(QProcess::ForwardedErrorChannel); - m_process->start(QStandardPaths::findExecutable(serverName)); + m_process->start(m_serverExecutableUrl.toLocalFile()); m_process->waitForStarted(); m_process->waitForReadyRead(); QTextStream stream(m_process->readAllStandardOutput()); const QString& readyStatus = QString::fromLatin1("ready"); while (m_process->state() == QProcess::Running) { const QString& rl = stream.readLine(); if (rl == readyStatus) break; } connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonSession::readOutput); connect(m_process, &QProcess::errorOccurred, this, &PythonSession::reportServerProcessError); sendCommand(QLatin1String("login")); QString dir; - if (!worksheetPath.isEmpty()) - dir = QFileInfo(worksheetPath).absoluteDir().absolutePath(); - sendCommand(QLatin1String("setFilePath"), QStringList() << worksheetPath << dir); + if (!m_worksheetPath.isEmpty()) + dir = QFileInfo(m_worksheetPath).absoluteDir().absolutePath(); + sendCommand(QLatin1String("setFilePath"), QStringList() << m_worksheetPath << dir); const QStringList& scripts = autorunScripts(); if(!scripts.isEmpty()){ QString autorunScripts = scripts.join(QLatin1String("\n")); evaluateExpression(autorunScripts, Cantor::Expression::DeleteOnFinish, true); variableModel()->update(); } changeStatus(Session::Done); emit loginDone(); } void PythonSession::logout() { if (!m_process) return; if (m_process->exitStatus() != QProcess::CrashExit && m_process->error() != QProcess::WriteError) sendCommand(QLatin1String("exit")); if(m_process->state() == QProcess::Running && !m_process->waitForFinished(1000)) { disconnect(m_process, &QProcess::errorOccurred, this, &PythonSession::reportServerProcessError); m_process->kill(); qDebug()<<"cantor_python server still running, process kill enforced"; } m_process->deleteLater(); m_process = nullptr; qDebug()<<"logout"; Session::logout(); } void PythonSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process && 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(); m_output.clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } Cantor::Expression* PythonSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; PythonExpression* expr = new PythonExpression(this, internal); changeStatus(Cantor::Session::Running); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } QSyntaxHighlighter* PythonSession::syntaxHighlighter(QObject* parent) { return new PythonHighlighter(parent, this, m_pythonVersion); } Cantor::CompletionObject* PythonSession::completionFor(const QString& command, int index) { return new PythonCompletionObject(command, index, this); } void PythonSession::runFirstExpression() { if (expressionQueue().isEmpty()) return; Cantor::Expression* expr = expressionQueue().first(); const QString command = expr->internalCommand(); qDebug() << "run first expression" << command; expr->setStatus(Cantor::Expression::Computing); if (expr->isInternal() && command.startsWith(QLatin1String("%variables "))) { const QString arg = command.section(QLatin1String(" "), 1); sendCommand(QLatin1String("model"), QStringList(arg)); } else sendCommand(QLatin1String("code"), QStringList(expr->internalCommand())); } void PythonSession::sendCommand(const QString& command, const QStringList arguments) const { qDebug() << "send command: " << command << arguments; const QString& message = command + recordSep + arguments.join(unitSep) + messageEnd; m_process->write(message.toLocal8Bit()); } void PythonSession::readOutput() { while (m_process->bytesAvailable() > 0) { const QByteArray& bytes = m_process->readAll(); if (m_pythonVersion == 3) m_output.append(QString::fromUtf8(bytes)); else if (m_pythonVersion == 2) m_output.append(QString::fromLocal8Bit(bytes)); else qCritical() << "Unsupported Python version" << m_pythonVersion; } qDebug() << "m_output: " << m_output; 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); for (const QString& message: packages) { if (expressionQueue().isEmpty()) break; const QString& output = message.section(unitSep, 0, 0); const QString& error = message.section(unitSep, 1, 1); bool isError = message.section(unitSep, 2, 2).toInt(); if (isError) { if(error.isEmpty()){ static_cast(expressionQueue().first())->parseOutput(output); } else { static_cast(expressionQueue().first())->parseError(error); } } else { static_cast(expressionQueue().first())->parseWarning(error); static_cast(expressionQueue().first())->parseOutput(output); } finishFirstExpression(true); } } void PythonSession::setWorksheetPath(const QString& path) { - worksheetPath = path; + m_worksheetPath = path; } void PythonSession::reportServerProcessError(QProcess::ProcessError serverError) { switch(serverError) { case QProcess::Crashed: emit error(i18n("Cantor Python server stopped working.")); break; case QProcess::FailedToStart: emit error(i18n("Failed to start Cantor python server.")); break; default: emit error(i18n("Communication with Cantor python server failed for unknown reasons.")); break; } reportSessionCrash(); } diff --git a/src/backends/python/pythonsession.h b/src/backends/python/pythonsession.h index 4b9f3258..86e8c737 100644 --- a/src/backends/python/pythonsession.h +++ b/src/backends/python/pythonsession.h @@ -1,70 +1,71 @@ /* 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) 2012 Filipe Saraiva Copyright (C) 2015 Minh Ngo */ #ifndef _PYTHONSESSION_H #define _PYTHONSESSION_H #include "session.h" #include #include +#include #include class CANTOR_PYTHONBACKEND_EXPORT PythonSession : public Cantor::Session { Q_OBJECT public: - PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName); + PythonSession(Cantor::Backend* backend, int pythonVersion, const QUrl serverExecutableUrl); ~PythonSession() override; void login() override; void logout() override; void interrupt() override; 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; void setWorksheetPath(const QString& path) override; virtual bool integratePlots() const = 0; virtual QStringList autorunScripts() const = 0; virtual bool variableManagement() const = 0; private: QProcess* m_process; - QString serverName; + QUrl m_serverExecutableUrl; - QString worksheetPath; + QString m_worksheetPath; int m_pythonVersion; QString m_output; private Q_SLOT: void readOutput(); void reportServerProcessError(QProcess::ProcessError serverError); private: void runFirstExpression() override; void sendCommand(const QString& command, const QStringList arguments = QStringList()) const; }; #endif /* _PYTHONSESSION_H */ diff --git a/src/backends/python/settings.ui b/src/backends/python/settings.ui index ac787f0b..ef432f01 100644 --- a/src/backends/python/settings.ui +++ b/src/backends/python/settings.ui @@ -1,81 +1,95 @@ PythonSettingsBase 0 0 400 300 + + + + + + Path to Cantor Python Server: + + + + + + + + Path to local documentation: Integrate Plots in Worksheet Let Cantor follow the creation/destruction of variables Enable Variable Management Scripts to autorun Qt::Vertical 20 40 KUrlRequester QFrame
kurlrequester.h
diff --git a/src/backends/python2/python2backend.cpp b/src/backends/python2/python2backend.cpp index f658e3df..bb76944f 100644 --- a/src/backends/python2/python2backend.cpp +++ b/src/backends/python2/python2backend.cpp @@ -1,96 +1,96 @@ /* 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) 2014, 2015 Minh Ngo Copyright (C) 2019 Alexander Semke */ #include "python2backend.h" #include "python2session.h" #include "settings.h" #include Python2Backend::Python2Backend(QObject* parent, const QList args) : PythonBackend(parent, args) { // Because the plugin may not have been loaded with // ExportExternalSymbols, we load the python symbols again // to make sure that python modules such as numpy see them // (see bug #330032) QLibrary pythonLib(QLatin1String("python2.7")); pythonLib.setLoadHints(QLibrary::ExportExternalSymbolsHint); pythonLib.load(); } Cantor::Session* Python2Backend::createSession() { return new Python2Session(this); } QString Python2Backend::id() const { return QLatin1String("python2"); } QString Python2Backend::version() const { return QLatin1String("2.7"); } Cantor::Backend::Capabilities Python2Backend::capabilities() const { Backend::Capabilities cap = Cantor::Backend::SyntaxHighlighting | Cantor::Backend::Completion | Cantor::Backend::SyntaxHelp; if(PythonSettings::variableManagement()) cap |= Cantor::Backend::VariableManagement; return cap; } QUrl Python2Backend::helpUrl() const { const QUrl& localDoc = PythonSettings::self()->localDoc(); if (!localDoc.isEmpty()) return localDoc; else return QUrl(i18nc("The url to the documentation Python 2", "https://docs.python.org/2/")); } QString Python2Backend::description() const { return i18n("Python is a remarkably powerful dynamic programming language that is used in a wide variety of application domains. " \ "There are several Python packages to scientific programming. " \ "This backend supports Python 2."); } KConfigSkeleton* Python2Backend::config() const { return PythonSettings::self(); } bool Python2Backend::requirementsFullfilled(QString* const reason) const { - const QString& path = QStandardPaths::findExecutable(QLatin1String("cantor_python2server")); + const QString& path = PythonSettings::pythonServerPath().toLocalFile(); return Cantor::Backend::checkExecutable(QLatin1String("Cantor Python2 Server"), path, reason); } K_PLUGIN_FACTORY_WITH_JSON(python2backend, "python2backend.json", registerPlugin();) #include "python2backend.moc" diff --git a/src/backends/python2/python2backend.kcfg b/src/backends/python2/python2backend.kcfg index 3b914afb..1d3e1485 100644 --- a/src/backends/python2/python2backend.kcfg +++ b/src/backends/python2/python2backend.kcfg @@ -1,23 +1,27 @@ + + + QUrl::fromLocalFile(QStandardPaths::findExecutable(QLatin1String("cantor_python2server"))) + false true diff --git a/src/backends/python2/python2session.cpp b/src/backends/python2/python2session.cpp index b220cafa..63a51857 100644 --- a/src/backends/python2/python2session.cpp +++ b/src/backends/python2/python2session.cpp @@ -1,51 +1,51 @@ /* 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 "python2session.h" #include "settings.h" #include "../python/pythonexpression.h" #include "../python/pythonkeywords.h" #include #include #include #include #include Python2Session::Python2Session(Cantor::Backend* backend) - : PythonSession(backend, 2, QLatin1String("cantor_python2server")) + : PythonSession(backend, 2, PythonSettings::pythonServerPath()) { } bool Python2Session::integratePlots() const { return PythonSettings::integratePlots(); } QStringList Python2Session::autorunScripts() const { return PythonSettings::autorunScripts(); } bool Python2Session::variableManagement() const { return PythonSettings::variableManagement(); } diff --git a/src/backends/python3/python3backend.cpp b/src/backends/python3/python3backend.cpp index 7fd9e91e..efd5210b 100644 --- a/src/backends/python3/python3backend.cpp +++ b/src/backends/python3/python3backend.cpp @@ -1,92 +1,92 @@ /* 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) 2014, 2015 Minh Ngo Copyright (C) 2019 Alexander Semke */ #include "python3backend.h" #include "python3session.h" #include "settings.h" #include Python3Backend::Python3Backend(QObject* parent, const QList& args) : PythonBackend(parent, args) { setObjectName(QLatin1String("python3backend")); } Cantor::Session* Python3Backend::createSession() { return new Python3Session(this); } QString Python3Backend::id() const { return QLatin1String("python3"); } QString Python3Backend::version() const { return QLatin1String("3.6"); } Cantor::Backend::Capabilities Python3Backend::capabilities() const { qDebug()<<"Requesting capabilities of Python3Session"; Backend::Capabilities cap = Cantor::Backend::SyntaxHighlighting | Cantor::Backend::Completion | Cantor::Backend::SyntaxHelp; if(PythonSettings::variableManagement()) cap |= Cantor::Backend::VariableManagement; return cap; } QUrl Python3Backend::helpUrl() const { const QUrl& localDoc = PythonSettings::self()->localDoc(); if (!localDoc.isEmpty()) return localDoc; else return QUrl(i18nc("The url to the documentation Python 3", "https://docs.python.org/3/")); } QString Python3Backend::description() const { return i18n("Python is a remarkably powerful dynamic programming language that is used in a wide variety of application domains. " \ "There are several Python packages to scientific programming. " \ "This backend supports Python 3."); } KConfigSkeleton* Python3Backend::config() const { return PythonSettings::self(); } bool Python3Backend::requirementsFullfilled(QString* const reason) const { - const QString& path = QStandardPaths::findExecutable(QLatin1String("cantor_python3server")); + const QString& path = PythonSettings::pythonServerPath().toLocalFile(); return Cantor::Backend::checkExecutable(QLatin1String("Cantor Python3 Server"), path, reason); } K_PLUGIN_FACTORY_WITH_JSON(python3backend, "python3backend.json", registerPlugin();) #include "python3backend.moc" diff --git a/src/backends/python3/python3backend.kcfg b/src/backends/python3/python3backend.kcfg index b4716ef6..860eac01 100644 --- a/src/backends/python3/python3backend.kcfg +++ b/src/backends/python3/python3backend.kcfg @@ -1,23 +1,28 @@ + QStandardPaths + + + QUrl::fromLocalFile(QStandardPaths::findExecutable(QLatin1String("cantor_python3server"))) + false true diff --git a/src/backends/python3/python3session.cpp b/src/backends/python3/python3session.cpp index 840a795e..7c223547 100644 --- a/src/backends/python3/python3session.cpp +++ b/src/backends/python3/python3session.cpp @@ -1,43 +1,43 @@ /* 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 "python3session.h" #include "settings.h" Python3Session::Python3Session(Cantor::Backend* backend) - : PythonSession(backend, 3, QLatin1String("cantor_python3server")) + : PythonSession(backend, 3, PythonSettings::pythonServerPath()) { } bool Python3Session::integratePlots() const { return PythonSettings::integratePlots(); } QStringList Python3Session::autorunScripts() const { return PythonSettings::autorunScripts(); } bool Python3Session::variableManagement() const { return PythonSettings::variableManagement(); }