diff --git a/src/backends/R/rexpression.cpp b/src/backends/R/rexpression.cpp index 234c539a..b413741d 100644 --- a/src/backends/R/rexpression.cpp +++ b/src/backends/R/rexpression.cpp @@ -1,151 +1,151 @@ /* 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) 2009 Alexander Rieder */ #include "rexpression.h" #include "textresult.h" #include "imageresult.h" #include "helpresult.h" #include "epsresult.h" #include "rsession.h" #include #include #include #include #include #include #include -RExpression::RExpression( Cantor::Session* session ) : Cantor::Expression(session) +RExpression::RExpression( Cantor::Session* session, bool internal ) : Cantor::Expression(session, internal) { } void RExpression::evaluate() { if(command().startsWith(QLatin1Char('?'))) m_isHelpRequest=true; else m_isHelpRequest=false; session()->enqueueExpression(this); } void RExpression::interrupt() { qDebug()<<"interrupting command"; setStatus(Cantor::Expression::Interrupted); } void RExpression::finished(int returnCode, const QString& text) { if (status() == Expression::Interrupted) return; if(returnCode==RExpression::SuccessCode) { qDebug() << "text: " << text; setResult(new Cantor::TextResult(text)); setStatus(Cantor::Expression::Done); }else if (returnCode==RExpression::ErrorCode) { qDebug() << "text: " << text; //setResult(new Cantor::TextResult(text)); setErrorMessage(text); setStatus(Cantor::Expression::Error); } } void RExpression::addInformation(const QString& information) { static_cast(session())->sendInputToServer(information); } void RExpression::showFilesAsResult(const QStringList& files) { qDebug()<<"showing files: "< */ #ifndef _REXPRESSION_H #define _REXPRESSION_H #include "expression.h" class RExpression : public Cantor::Expression { Q_OBJECT public: enum ServerReturnCode{SuccessCode=0, ErrorCode, InterruptedCode}; - RExpression( Cantor::Session*); + RExpression( Cantor::Session*, bool internal = false); ~RExpression() override = default; void evaluate() override; void interrupt() override; void addInformation(const QString&) override; public Q_SLOTS: void finished(int returnCode, const QString& text); void showFilesAsResult(const QStringList& files); private: bool m_isHelpRequest; }; #endif /* _REXPRESSION_H */ diff --git a/src/backends/R/rsession.cpp b/src/backends/R/rsession.cpp index e60b265c..144b1995 100644 --- a/src/backends/R/rsession.cpp +++ b/src/backends/R/rsession.cpp @@ -1,206 +1,206 @@ /* 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) 2009 Alexander Rieder Copyright (C) 2018 Alexander Semke */ #include "rsession.h" #include "rexpression.h" #include "rcompletionobject.h" #include "rhighlighter.h" #include #include #include #include #ifndef Q_OS_WIN #include #endif RSession::RSession(Cantor::Backend* backend) : Session(backend), m_process(nullptr), m_rServer(nullptr), m_variableModel(new Cantor::DefaultVariableModel(this)) { } RSession::~RSession() { if (m_process) m_process->terminate(); } void RSession::login() { qDebug()<<"login"; emit loginStarted(); if(m_process) m_process->deleteLater(); m_process = new QProcess(this); m_process->start(QStandardPaths::findExecutable(QLatin1String("cantor_rserver"))); m_process->waitForStarted(); m_process->waitForReadyRead(); qDebug()<readAllStandardOutput(); m_rServer = new org::kde::Cantor::R(QString::fromLatin1("org.kde.Cantor.R-%1").arg(m_process->pid()), QLatin1String("/"), QDBusConnection::sessionBus(), this); connect(m_rServer, SIGNAL(statusChanged(int)), this, SLOT(serverChangedStatus(int))); connect(m_rServer, SIGNAL(symbolList(const QStringList&, const QStringList&, const QStringList&)),this,SLOT(receiveSymbols(const QStringList&, const QStringList&, const QStringList&))); emit loginDone(); qDebug()<<"login done"; } void RSession::logout() { qDebug()<<"logout"; m_process->terminate(); } void RSession::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 } expressionQueue().first()->interrupt(); expressionQueue().removeFirst(); foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Done); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } -Cantor::Expression* RSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) +Cantor::Expression* RSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug()<<"evaluating: "<setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } Cantor::CompletionObject* RSession::completionFor(const QString& command, int index) { RCompletionObject *cmp=new RCompletionObject(command, index, this); connect(m_rServer,SIGNAL(completionFinished(const QString&,const QStringList&)),cmp,SLOT(receiveCompletions(const QString&,const QStringList&))); connect(cmp,SIGNAL(requestCompletion(const QString&)),m_rServer,SLOT(completeCommand(const QString&))); return cmp; } QSyntaxHighlighter* RSession::syntaxHighlighter(QObject* parent) { RHighlighter *h=new RHighlighter(parent); connect(h,SIGNAL(syntaxRegExps(QVector&,QVector&)),this,SLOT(fillSyntaxRegExps(QVector&,QVector&))); connect(this,SIGNAL(symbolsChanged()),h,SLOT(refreshSyntaxRegExps())); return h; } void RSession::fillSyntaxRegExps(QVector& v, QVector& f) { // WARNING: current implementation as-in-maxima is a performance hit // think about grouping expressions together or only fetching needed ones v.clear(); f.clear(); foreach (const QString s, m_variables) if (!s.contains(QRegExp(QLatin1String("[^A-Za-z0-9_.]")))) v.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); foreach (const QString s, m_functions) if (!s.contains(QRegExp(QLatin1String("[^A-Za-z0-9_.]")))) f.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); } void RSession::receiveSymbols(const QStringList& vars, const QStringList& values, const QStringList & funcs) { m_variables = vars; for (int i = 0; i < vars.count(); i++) { m_variableModel->addVariable(vars[i], values[i]); } m_functions = funcs; emit symbolsChanged(); } void RSession::serverChangedStatus(int status) { qDebug()<<"changed status to "<(expressionQueue().takeFirst()); qDebug()<<"done running "<command(); } if(expressionQueue().isEmpty()) changeStatus(Cantor::Session::Done); else runFirstExpression(); } else changeStatus(Cantor::Session::Running); } void RSession::runFirstExpression() { if (expressionQueue().isEmpty()) return; disconnect(m_rServer, SIGNAL(expressionFinished(int, const QString&)), nullptr, nullptr); disconnect(m_rServer, SIGNAL(inputRequested(const QString&)), nullptr, nullptr); disconnect(m_rServer, SIGNAL(showFilesNeeded(const QStringList&)), nullptr, nullptr); qDebug()<<"size: "<(expressionQueue().first()); qDebug()<<"running expression: "<command(); connect(m_rServer, SIGNAL(expressionFinished(int, const QString &)), expr, SLOT(finished(int, const QString&))); connect(m_rServer, SIGNAL(inputRequested(const QString&)), expr, SIGNAL(needsAdditionalInformation(const QString&))); connect(m_rServer, SIGNAL(showFilesNeeded(const QStringList&)), expr, SLOT(showFilesAsResult(const QStringList&))); expr->setStatus(Cantor::Expression::Computing); m_rServer->runCommand(expr->command()); } void RSession::sendInputToServer(const QString& input) { QString s=input; if(!input.endsWith(QLatin1Char('\n'))) s+=QLatin1Char('\n'); m_rServer->answerRequest(s); } QAbstractItemModel* RSession::variableModel() { return m_variableModel; } diff --git a/src/backends/R/rsession.h b/src/backends/R/rsession.h index 6f5a6be7..690ee6c6 100644 --- a/src/backends/R/rsession.h +++ b/src/backends/R/rsession.h @@ -1,74 +1,74 @@ /* 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) 2009 Alexander Rieder */ #ifndef _RSESSION_H #define _RSESSION_H #include #include #include "session.h" #include "rserver_interface.h" class RExpression; class QProcess; namespace Cantor { class DefaultVariableModel; } class RSession : public Cantor::Session { Q_OBJECT public: RSession( Cantor::Backend* backend); ~RSession() override; void login() override; void logout() override; void interrupt() override; - Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave) 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; QAbstractItemModel* variableModel() override; void sendInputToServer(const QString& input); void runFirstExpression() override; protected Q_SLOTS: void serverChangedStatus(int status); void receiveSymbols(const QStringList& vars, const QStringList& values, const QStringList & funcs); void fillSyntaxRegExps(QVector& v, QVector& f); Q_SIGNALS: void symbolsChanged(); private: QProcess* m_process; org::kde::Cantor::R* m_rServer; /* Available variables and functions, TODO make full classes and type info */ Cantor::DefaultVariableModel* m_variableModel; QStringList m_variables; QStringList m_functions; }; #endif /* _RSESSION_H */ diff --git a/src/backends/julia/juliaexpression.cpp b/src/backends/julia/juliaexpression.cpp index 9a452025..eeabc7e5 100644 --- a/src/backends/julia/juliaexpression.cpp +++ b/src/backends/julia/juliaexpression.cpp @@ -1,104 +1,104 @@ /* 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) 2016 Ivan Lakhtanov */ #include "juliaexpression.h" #include #include #include "settings.h" #include "juliasession.h" #include "juliakeywords.h" #include "textresult.h" #include "imageresult.h" -JuliaExpression::JuliaExpression(Cantor::Session *session) - : Cantor::Expression(session) +JuliaExpression::JuliaExpression(Cantor::Session *session, bool internal) + : Cantor::Expression(session, internal) { } void JuliaExpression::evaluate() { setStatus(Cantor::Expression::Computing); auto juliaSession = dynamic_cast(session()); // Plots integration m_plot_filename.clear(); if (juliaSession->integratePlots() && checkPlotShowingCommands()) { // Simply add plot saving command to the end of execution QStringList inlinePlotFormats; inlinePlotFormats << QLatin1String("svg"); inlinePlotFormats << QLatin1String("eps"); inlinePlotFormats << QLatin1String("png"); auto inlinePlotFormat = inlinePlotFormats[JuliaSettings::inlinePlotFormat()]; m_plot_filename = QDir::tempPath() + QString::fromLatin1("/cantor-julia-export-%1.%2") .arg(QUuid::createUuid().toString()).arg(inlinePlotFormat); QString saveFigCommand = QString::fromLatin1("\nGR.savefig(\"%1\")\n").arg(m_plot_filename); setCommand(command().append(saveFigCommand)); } juliaSession->runExpression(this); } void JuliaExpression::finalize() { auto juliaSession = dynamic_cast(session()); setErrorMessage( juliaSession->getError() .replace(QLatin1String("\n"), QLatin1String("
")) ); if (juliaSession->getWasException()) { setResult(new Cantor::TextResult(juliaSession->getOutput())); setStatus(Cantor::Expression::Error); } else { if (!m_plot_filename.isEmpty() && QFileInfo(m_plot_filename).exists()) { // If we have plot in result, show it setResult( new Cantor::ImageResult(QUrl::fromLocalFile(m_plot_filename))); QDir().remove(m_plot_filename); } else { setResult(new Cantor::TextResult(juliaSession->getOutput())); } setStatus(Cantor::Expression::Done); } } void JuliaExpression::interrupt() { setStatus(Cantor::Expression::Interrupted); } bool JuliaExpression::checkPlotShowingCommands() { for (auto showingCommand : JuliaKeywords::instance()->plotShowingCommands()) { if (command().contains(showingCommand + QLatin1String("("))) { return true; } } return false; } diff --git a/src/backends/julia/juliaexpression.h b/src/backends/julia/juliaexpression.h index b619bbf9..3d5a140e 100644 --- a/src/backends/julia/juliaexpression.h +++ b/src/backends/julia/juliaexpression.h @@ -1,68 +1,69 @@ /* 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) 2016 Ivan Lakhtanov */ #pragma once #include "expression.h" /** * Expression of Julia language */ class JuliaExpression: public Cantor::Expression { Q_OBJECT public: /** * Creates new JuliaExpression * * @param session session to bound expression to + * @param internal @see Cantor::Expression::Expression(Session*, bool) */ - JuliaExpression(Cantor::Session *session); + JuliaExpression(Cantor::Session *session, bool internal = false); ~JuliaExpression() override = default; /** * @see Cantor::Expression::evaluate */ void evaluate() override; /** * @see Cantor::Expression::interrupt */ void interrupt() override; /** * Call this function from session when JuliaServer ends evaluation of * this expression. * * This checks inline plots, exceptions and set appropriate result */ void finalize(); private: /// If not empty, it's a filename of plot image file expression is awaiting /// to get QString m_plot_filename; /** * @return bool indicator if current expression contains command that * shows plot */ bool checkPlotShowingCommands(); }; diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp index ead243d9..aa6a84a1 100644 --- a/src/backends/julia/juliasession.cpp +++ b/src/backends/julia/juliasession.cpp @@ -1,280 +1,281 @@ /* 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) 2016 Ivan Lakhtanov */ #include "juliasession.h" #include #include #include #include #include #include "defaultvariablemodel.h" #include "juliaexpression.h" #include "settings.h" #include "juliahighlighter.h" #include "juliakeywords.h" #include "juliaextensions.h" #include "juliabackend.h" #include "juliacompletionobject.h" #include const QRegularExpression JuliaSession::typeVariableInfo = QRegularExpression(QLatin1String("\\w+\\[")); JuliaSession::JuliaSession(Cantor::Backend *backend) : Session(backend) , m_process(nullptr) , m_interface(nullptr) , m_currentExpression(nullptr) , m_variableModel(new Cantor::DefaultVariableModel(this)) { } void JuliaSession::login() { emit loginStarted(); if (m_process) { m_process->deleteLater(); } m_process = new KProcess(this); m_process->setOutputChannelMode(KProcess::SeparateChannels); (*m_process) << QStandardPaths::findExecutable(QLatin1String("cantor_juliaserver")); m_process->start(); m_process->waitForStarted(); m_process->waitForReadyRead(); QTextStream stream(m_process->readAllStandardOutput()); QString readyStatus = QLatin1String("ready"); 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 = QString::fromLatin1("org.kde.Cantor.Julia-%1").arg(m_process->pid()); m_interface = new QDBusInterface( serviceName, QString::fromLatin1("/"), QString(), QDBusConnection::sessionBus() ); if (!m_interface->isValid()) { qWarning() << QDBusConnection::sessionBus().lastError().message(); return; } m_interface->call( QString::fromLatin1("login"), JuliaSettings::self()->replPath().path() ); listVariables(); // Plots integration if (integratePlots()) { runJuliaCommand( QLatin1String("import GR; ENV[\"GKS_WSTYPE\"] = \"nul\"") ); } emit loginDone(); qDebug() << "login to julia " << JULIA_VERSION_STRING << "done"; } void JuliaSession::logout() { m_process->terminate(); JuliaKeywords::instance()->clearVariables(); JuliaKeywords::instance()->clearFunctions(); } void JuliaSession::interrupt() { if (m_process->pid()) { m_process->kill(); } for (Cantor::Expression *e : m_runningExpressions) { e->interrupt(); } m_runningExpressions.clear(); changeStatus(Cantor::Session::Done); } Cantor::Expression *JuliaSession::evaluateExpression( const QString &cmd, - Cantor::Expression::FinishingBehavior behave) + Cantor::Expression::FinishingBehavior behave, + bool internal) { - JuliaExpression *expr = new JuliaExpression(this); + JuliaExpression *expr = new JuliaExpression(this, internal); changeStatus(Cantor::Session::Running); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } Cantor::CompletionObject *JuliaSession::completionFor( const QString &command, int index) { return new JuliaCompletionObject(command, index, this); } QSyntaxHighlighter *JuliaSession::syntaxHighlighter(QObject *parent) { JuliaHighlighter *highlighter = new JuliaHighlighter(parent); QObject::connect( this, SIGNAL(updateHighlighter()), highlighter, SLOT(updateHighlight()) ); return highlighter; } void JuliaSession::runJuliaCommand(const QString &command) const { m_interface->call(QLatin1String("runJuliaCommand"), command); } void JuliaSession::runJuliaCommandAsync(const QString &command) { m_interface->callWithCallback( QLatin1String("runJuliaCommand"), {command}, this, SLOT(onResultReady()) ); } void JuliaSession::onResultReady() { m_currentExpression->finalize(); m_runningExpressions.removeAll(m_currentExpression); listVariables(); changeStatus(Cantor::Session::Done); } void JuliaSession::runExpression(JuliaExpression *expr) { m_runningExpressions.append(expr); m_currentExpression = expr; runJuliaCommandAsync(expr->command()); } QString JuliaSession::getStringFromServer(const QString &method) { const QDBusReply &reply = m_interface->call(method); return (reply.isValid() ? reply.value() : reply.error().message()); } QString JuliaSession::getOutput() { return getStringFromServer(QLatin1String("getOutput")); } QString JuliaSession::getError() { return getStringFromServer(QLatin1String("getError")); } bool JuliaSession::getWasException() { const QDBusReply &reply = m_interface->call(QLatin1String("getWasException")); return reply.isValid() && reply.value(); } void JuliaSession::listVariables() { JuliaKeywords::instance()->clearVariables(); JuliaKeywords::instance()->clearFunctions(); m_interface->call(QLatin1String("parseModules")); const QStringList& variables = static_cast>(m_interface->call(QLatin1String("variablesList"))).value(); const QStringList& values = static_cast>(m_interface->call(QLatin1String("variableValuesList"))).value(); for (int i = 0; i < variables.size(); i++) { if (i >= values.size()) { qWarning() << "Don't have value for variable from julia server response, somethinkg wrong!"; continue; } const QString& name = variables[i]; QString value = values[i]; if (value != JuliaVariableManagementExtension::REMOVED_VARIABLE_MARKER) { // Register variable // We use replace here, because julia return data type for some variables, and we need // remove it to make variable view more consistent with the other backends // More info: https://bugs.kde.org/show_bug.cgi?id=377771 m_variableModel->addVariable(name, value.replace(typeVariableInfo,QLatin1String("["))); JuliaKeywords::instance()->addVariable(name); } else m_variableModel->removeVariable(name); } const QStringList& functions = static_cast>(m_interface->call(QLatin1String("functionsList"))).value(); foreach (const QString& name, functions) { JuliaKeywords::instance()->addFunction(name); } emit updateHighlighter(); } QAbstractItemModel *JuliaSession::variableModel() { return m_variableModel; } bool JuliaSession::integratePlots() { return JuliaSettings::integratePlots(); } diff --git a/src/backends/julia/juliasession.h b/src/backends/julia/juliasession.h index 211c66dd..f17daf0f 100644 --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -1,174 +1,175 @@ /* 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) 2016 Ivan Lakhtanov */ #pragma once #include #include #include "session.h" class JuliaExpression; class JuliaCompletionObject; class KProcess; class QDBusInterface; namespace Cantor { class DefaultVariableModel; } /** * Implements a Cantor session for the Julia backend * * It communicates through DBus interface with JuliaServer */ class JuliaSession: public Cantor::Session { Q_OBJECT public: /** * Constructs session * * @param backend owning backend */ JuliaSession(Cantor::Backend *backend); /** * @see Cantor::Session::login */ void login() override; /** * @see Cantor::Session::logout */ void logout() override; /** * @see Cantor::Session::interrupt */ void interrupt() override; /** * @see Cantor::Session::evaluateExpression */ Cantor::Expression *evaluateExpression( const QString &command, - Cantor::Expression::FinishingBehavior behave) override; + Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, + bool internal = false) override; /** * @see Cantor::Session::completionFor */ Cantor::CompletionObject *completionFor( const QString &cmd, int index = -1) override; /** * @see Cantor::Session::syntaxHighlighter */ QSyntaxHighlighter *syntaxHighlighter(QObject *parent) override; /** * @see Cantor::Session::variableModel */ QAbstractItemModel *variableModel() override; /** * @return indicator if config says to integrate plots into worksheet */ bool integratePlots(); Q_SIGNALS: /** * Emit this to update syntax highlighter */ void updateHighlighter(); private Q_SLOTS: /** * Called when async call to JuliaServer is finished */ void onResultReady(); private: KProcess *m_process; //< process to run JuliaServer inside QDBusInterface *m_interface; //< interface to JuliaServer /// Expressions running at the moment QList m_runningExpressions; JuliaExpression *m_currentExpression; //< current expression /// Variable management model Cantor::DefaultVariableModel *m_variableModel; static const QRegularExpression typeVariableInfo; /// Cache to speedup modules whos calls QMap m_whos_cache; friend JuliaExpression; friend JuliaCompletionObject; /** * Runs Julia expression * * @param expression expression to run */ void runExpression(JuliaExpression *expression); /** * Runs Julia piece of code in synchronous mode * * @param command command to execute */ void runJuliaCommand(const QString &command) const; /** * Runs Julia piece of code in asynchronous mode. When finished * onResultReady is called * * @param command command to execute */ void runJuliaCommandAsync(const QString &command); /** * Helper method to get QString returning function result * * @param method DBus method to call * @return result of the method */ QString getStringFromServer(const QString &method); /** * @return stdout of the last executed command */ QString getOutput(); /** * @return stderr of the last executed command */ QString getError(); /** * @return indicator of exception occurred during the last command execution */ bool getWasException(); /** * Updates variable model by querying all modules in scope with whos command */ void listVariables(); }; diff --git a/src/backends/kalgebra/kalgebraexpression.cpp b/src/backends/kalgebra/kalgebraexpression.cpp index a7dbc060..252bc691 100644 --- a/src/backends/kalgebra/kalgebraexpression.cpp +++ b/src/backends/kalgebra/kalgebraexpression.cpp @@ -1,62 +1,62 @@ /************************************************************************************* * Copyright (C) 2009 by Aleix Pol * * * * 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 * *************************************************************************************/ #include "kalgebraexpression.h" #include "textresult.h" #include "helpresult.h" #include "kalgebrasession.h" #include #include #include #include -KAlgebraExpression::KAlgebraExpression( KAlgebraSession* session ) - : Cantor::Expression(session) +KAlgebraExpression::KAlgebraExpression( KAlgebraSession* session, bool internal) + : Cantor::Expression(session, internal) {} void KAlgebraExpression::evaluate() { setStatus(Cantor::Expression::Computing); Analitza::Analyzer* a=static_cast(session())->analyzer(); Analitza::Expression res; QString cmd = command(); QTextStream stream(&cmd); Analitza::ExpressionStream s(&stream); for(; !s.atEnd();) { a->setExpression(s.next()); res = a->evaluate(); if(!a->isCorrect()) break; } if(a->isCorrect()) { setResult(new Cantor::TextResult(res.toString())); setStatus(Cantor::Expression::Done); } else { setErrorMessage(i18n("Error: %1", a->errors().join(QLatin1String("\n")))); setStatus(Cantor::Expression::Error); } } void KAlgebraExpression::interrupt() {} diff --git a/src/backends/kalgebra/kalgebraexpression.h b/src/backends/kalgebra/kalgebraexpression.h index 28a1f0d3..e77aced9 100644 --- a/src/backends/kalgebra/kalgebraexpression.h +++ b/src/backends/kalgebra/kalgebraexpression.h @@ -1,37 +1,37 @@ /************************************************************************************* * Copyright (C) 2009 by Aleix Pol * * * * 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 * *************************************************************************************/ #ifndef KALGEBRA_EXPRESSION_H #define KALGEBRA_EXPRESSION_H #include "expression.h" class KAlgebraSession; class KAlgebraExpression : public Cantor::Expression { Q_OBJECT public: - KAlgebraExpression( KAlgebraSession* session); + KAlgebraExpression( KAlgebraSession* session, bool internal = false); ~KAlgebraExpression() override = default; void evaluate() override; void interrupt() override; }; #endif diff --git a/src/backends/kalgebra/kalgebrasession.cpp b/src/backends/kalgebra/kalgebrasession.cpp index d62e1297..2930fd05 100644 --- a/src/backends/kalgebra/kalgebrasession.cpp +++ b/src/backends/kalgebra/kalgebrasession.cpp @@ -1,110 +1,111 @@ /************************************************************************************* * Copyright (C) 2009 by Aleix Pol * * * * 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 * *************************************************************************************/ #include "settings.h" #include "kalgebrasession.h" #include "kalgebraexpression.h" #include "kalgebracompletionobject.h" #include #include #include #include #include "kalgebrasyntaxhelpobject.h" #include #include KAlgebraSession::KAlgebraSession( Cantor::Backend* backend) : Session(backend) { m_analyzer = new Analitza::Analyzer; m_operatorsModel = new OperatorsModel; m_variablesModel = new Analitza::VariablesModel(m_analyzer->variables()); m_operatorsModel->setVariables(m_analyzer->variables()); } KAlgebraSession::~KAlgebraSession() { delete m_analyzer; } void KAlgebraSession::login() { emit loginStarted(); if(!KAlgebraSettings::autorunScripts().isEmpty()){ QString autorunScripts = KAlgebraSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, KAlgebraExpression::DeleteOnFinish); } changeStatus(Cantor::Session::Done); emit loginDone(); } void KAlgebraSession::logout() {} void KAlgebraSession::interrupt() { changeStatus(Cantor::Session::Done); } Cantor::Expression* KAlgebraSession::evaluateExpression(const QString& cmd, - Cantor::Expression::FinishingBehavior behave) + Cantor::Expression::FinishingBehavior behave, + bool internal) { - KAlgebraExpression* expr=new KAlgebraExpression(this); + KAlgebraExpression* expr=new KAlgebraExpression(this, internal); expr->setFinishingBehavior(behave); changeStatus(Cantor::Session::Running); expr->setCommand(cmd); expr->evaluate(); changeStatus(Cantor::Session::Done); m_operatorsModel->setVariables(m_analyzer->variables()); m_variablesModel->updateInformation(); return expr; } Cantor::CompletionObject* KAlgebraSession::completionFor(const QString& command, int index) { return new KAlgebraCompletionObject(command, index, this); } Cantor::SyntaxHelpObject* KAlgebraSession::syntaxHelpFor(const QString& cmd) { return new KAlgebraSyntaxHelpObject(cmd, this); } OperatorsModel* KAlgebraSession::operatorsModel() { return m_operatorsModel; } QSyntaxHighlighter* KAlgebraSession::syntaxHighlighter(QObject* parent) { Q_UNUSED(parent); //return new AlgebraHighlighter(parent->document()); // TODO: Think of something better here. return new AlgebraHighlighter(nullptr); } QAbstractItemModel* KAlgebraSession::variableModel() { return m_variablesModel; } diff --git a/src/backends/kalgebra/kalgebrasession.h b/src/backends/kalgebra/kalgebrasession.h index efc10512..d081dd8d 100644 --- a/src/backends/kalgebra/kalgebrasession.h +++ b/src/backends/kalgebra/kalgebrasession.h @@ -1,58 +1,58 @@ /************************************************************************************* * Copyright (C) 2009 by Aleix Pol * * * * 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 * *************************************************************************************/ #ifndef KALGEBRA_SESSION_H #define KALGEBRA_SESSION_H #include "session.h" class OperatorsModel; class KAlgebraExpression; namespace Analitza { class Analyzer; class VariablesModel; } class KAlgebraSession : public Cantor::Session { Q_OBJECT public: KAlgebraSession( Cantor::Backend* backend); ~KAlgebraSession() override; void login() override; void logout() override; void interrupt() override; - Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave) override; + Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& cmd) override; Analitza::Analyzer* analyzer() const { return m_analyzer; } OperatorsModel* operatorsModel(); QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; QAbstractItemModel* variableModel() override; private: Analitza::Analyzer* m_analyzer; OperatorsModel* m_operatorsModel; Analitza::VariablesModel* m_variablesModel; }; #endif diff --git a/src/backends/lua/luaexpression.cpp b/src/backends/lua/luaexpression.cpp index 95abfe7c..338e94d9 100644 --- a/src/backends/lua/luaexpression.cpp +++ b/src/backends/lua/luaexpression.cpp @@ -1,108 +1,108 @@ /* 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 Lucas Hermann Negri */ #include "luaexpression.h" #include "luasession.h" #include "luahelper.h" #include "textresult.h" #include "imageresult.h" #include "helpresult.h" #include #include #include #include -LuaExpression::LuaExpression( Cantor::Session* session) - : Cantor::Expression(session) +LuaExpression::LuaExpression( Cantor::Session* session, bool internal) + : Cantor::Expression(session, internal) { } void LuaExpression::evaluate() { /* * start evaluating the current expression * set the status to computing * decide what needs to be done if the user is trying to define a function etc */ setStatus(Cantor::Expression::Computing); if (command().isEmpty()) { setStatus(Cantor::Expression::Done); return; } LuaSession* currentSession = dynamic_cast(session()); currentSession->runExpression(this); } void LuaExpression::parseError(QString &error) { qDebug() << error; setErrorMessage(error); setStatus(Error); } void LuaExpression::parseOutput(QString &output) { qDebug()<<"parsing the output " << output; const QStringList& inputs = command().split(QLatin1Char('\n')); const QStringList& outputs = output.split(QLatin1Char('\n')); QString parsedOutput; for (auto out : outputs) { //remove lua's promt characters if available if (out.startsWith(QLatin1String("> "))) out.remove(0, 2); else if (out.startsWith(QLatin1String(">> "))) out.remove(0, 3); //for multi-line inputs lua returs the inputs as part of the output //-> remove the input lines from the output. //since lua doesn't always seem to preserve the spaces, compare trimmed strings out = out.trimmed(); bool found = false; for (auto in : inputs) { if (out == in.trimmed()) { found = true; break; } } if (!found) { if (!parsedOutput.isEmpty()) parsedOutput += QLatin1Char('\n'); parsedOutput += out; } } qDebug() << "final output of the command " << command() << ": " << parsedOutput << endl; if (!parsedOutput.isEmpty()) setResult(new Cantor::TextResult(parsedOutput)); setStatus(Cantor::Expression::Done); } void LuaExpression::interrupt() { setStatus(Cantor::Expression::Interrupted); } diff --git a/src/backends/lua/luaexpression.h b/src/backends/lua/luaexpression.h index ad80c4ef..af51bd5a 100644 --- a/src/backends/lua/luaexpression.h +++ b/src/backends/lua/luaexpression.h @@ -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) 2014 Lucas Hermann Negri */ #ifndef _LUAEXPRESSION_H #define _LUAEXPRESSION_H #include "expression.h" struct lua_State; class LuaExpression : public Cantor::Expression { Q_OBJECT public: - LuaExpression( Cantor::Session* session); + LuaExpression( Cantor::Session* session, bool internal = false); ~LuaExpression() override = default; void evaluate() override; void interrupt() override; void parseOutput(QString& output); void parseError(QString& error); }; #endif /* _LUAEXPRESSION_H */ diff --git a/src/backends/lua/luasession.cpp b/src/backends/lua/luasession.cpp index e9b2cdd9..b3aa8d87 100644 --- a/src/backends/lua/luasession.cpp +++ b/src/backends/lua/luasession.cpp @@ -1,194 +1,194 @@ /* 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 Lucas Hermann Negri */ #include "luasession.h" #include "luaexpression.h" #include "luacompletionobject.h" #include "luahighlighter.h" #include "luahelper.h" #include #include "ui_settings.h" #include LuaSession::LuaSession( Cantor::Backend* backend) : Session(backend), m_process(nullptr), m_currentExpression(nullptr) { } void LuaSession::login() { emit loginStarted(); /* * setup Qprocess here * load the autoscripts */ m_process = new QProcess(this); m_process->setProgram(QLatin1String("/usr/bin/lua")); m_process->setArguments(QStringList() << QLatin1String("-i")); m_process->setProcessChannelMode(QProcess::SeparateChannels); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readIntroMessage())); connect(m_process, SIGNAL(started()), this, SLOT(processStarted())); m_process->start(); m_process->waitForStarted(); m_process->waitForReadyRead(); // we need this for tab completion m_L = luaL_newstate(); luaL_openlibs(m_L); changeStatus(Cantor::Session::Done); emit loginDone(); } void LuaSession::readIntroMessage() { while(m_process->bytesAvailable()) { m_output.append(QString::fromLocal8Bit(m_process->readLine())); } if(!m_output.isEmpty() && m_output.trimmed().endsWith(QLatin1String(">"))) { qDebug() << " reading the intro message " << m_output ; m_output.clear(); disconnect(m_process, SIGNAL(readyReadStandardOutput()), this , SLOT(readIntroMessage())); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readError())); } } void LuaSession::readOutput() { /* * parse the output * clear all the garbage * set it as output */ // keep reading till the output ends with '>'. // '>' marks the end of output for a particular command; while(m_process->bytesAvailable()) { m_output.append(QString::fromLocal8Bit(m_process->readLine())); } //merge outputs until lua's promt "> " appears, take care of the comment characters "-->" and subprompt ">> " if(m_currentExpression && !m_output.isEmpty() && m_output.endsWith(QLatin1String("> ")) && !m_output.endsWith(QLatin1String("-> ")) && !m_output.endsWith(QLatin1String(">> "))) { // we have our complete output // clean the output and parse it and clear m_output; m_currentExpression->parseOutput(m_output); m_output.clear(); } } void LuaSession::readError() { qDebug() << "readError"; QString error = QString::fromLocal8Bit(m_process->readAllStandardError()); if (!m_currentExpression || error.isEmpty()) { return; } m_currentExpression->parseError(error); } void LuaSession::processStarted() { qDebug() << m_process->program() << " pid " << m_process->processId() << " started " << endl; } void LuaSession::logout() { if(m_process) m_process->kill(); } void LuaSession::interrupt() { // Lua backend is synchronous, there is no way to currently interrupt an expression (in a clean way) changeStatus(Cantor::Session::Done); } -Cantor::Expression* LuaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) +Cantor::Expression* LuaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { changeStatus(Cantor::Session::Running); - m_currentExpression = new LuaExpression(this); + m_currentExpression = new LuaExpression(this, internal); connect(m_currentExpression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(expressionFinished(Cantor::Expression::Status))); m_currentExpression->setFinishingBehavior(behave); m_currentExpression->setCommand(cmd); m_currentExpression->evaluate(); return m_currentExpression; } void LuaSession::runExpression(LuaExpression *currentExpression) { /* * get the current command * format it and write to m_process */ QString command = currentExpression->command(); command += QLatin1String("\n"); qDebug() << "final command to be executed " << command << endl; m_process->write(command.toLocal8Bit()); } Cantor::CompletionObject* LuaSession::completionFor(const QString& command, int index) { return new LuaCompletionObject(command, index, this); } void LuaSession::expressionFinished(Cantor::Expression::Status status) { switch(status) { case Cantor::Expression::Computing: break; case Cantor::Expression::Done: case Cantor::Expression::Error: case Cantor::Expression::Interrupted: changeStatus(Cantor::Session::Done); break; } } QSyntaxHighlighter* LuaSession::syntaxHighlighter(QObject* parent) { LuaHighlighter* highlighter = new LuaHighlighter(parent); return highlighter; } lua_State* LuaSession::getState() const { return m_L; } diff --git a/src/backends/lua/luasession.h b/src/backends/lua/luasession.h index c01746f8..f6f23478 100644 --- a/src/backends/lua/luasession.h +++ b/src/backends/lua/luasession.h @@ -1,65 +1,65 @@ /* 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 Lucas Hermann Negri */ #ifndef _LUASESSION_H #define _LUASESSION_H #include "session.h" #include class LuaExpression; class QProcess; class LuaSession : public Cantor::Session { Q_OBJECT public: LuaSession( Cantor::Backend* backend); ~LuaSession() override = default; void login() override; void logout() override; void interrupt() override; void runExpression(LuaExpression* currentExpression); - Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave) override; + Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; lua_State* getState() const; public Q_SLOTS: void readIntroMessage(); void readOutput(); void readError(); void processStarted(); private Q_SLOTS: void expressionFinished(Cantor::Expression::Status status); private: lua_State* m_L; QProcess* m_process; LuaExpression* m_currentExpression; QString m_output; }; #endif /* _LUASESSION_H */ diff --git a/src/backends/maxima/maximaexpression.cpp b/src/backends/maxima/maximaexpression.cpp index 0bc3fd7f..0aa315a0 100644 --- a/src/backends/maxima/maximaexpression.cpp +++ b/src/backends/maxima/maximaexpression.cpp @@ -1,785 +1,785 @@ /* 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) 2009-2012 Alexander Rieder */ #include "maximaexpression.h" #include #include "maximasession.h" #include "textresult.h" #include "epsresult.h" #include "imageresult.h" #include "helpresult.h" #include "latexresult.h" #include "settings.h" #include #include #include #include #include #include #include #include -MaximaExpression::MaximaExpression( Cantor::Session* session ) : Cantor::Expression(session), +MaximaExpression::MaximaExpression( Cantor::Session* session, bool internal ) : Cantor::Expression(session, internal), m_tempFile(nullptr), m_isHelpRequest(false), m_isPlot(false), m_gotErrorContent(false) { } void MaximaExpression::evaluate() { //until we get the real output Id from maxima, set it to invalid setId(-1); m_isHelpRequest=false; m_isPlot=false; m_gotErrorContent=false; if(m_tempFile) m_tempFile->deleteLater(); m_tempFile=nullptr; //check if this is a ?command if(command().startsWith(QLatin1String("??")) || command().startsWith(QLatin1String("describe(")) || command().startsWith(QLatin1String("example(")) || command().startsWith(QLatin1String(":lisp(cl-info::info-exact"))) m_isHelpRequest=true; if(command().contains(QRegExp(QLatin1String("(?:plot2d|plot3d|contour_plot)\\s*\\([^\\)]"))) && MaximaSettings::self()->integratePlots() && !command().contains(QLatin1String("ps_file"))) { m_isPlot=true; #ifdef WITH_EPS m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_maxima-XXXXXX.eps" )); #else m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_maxima-XXXXXX.png")); #endif m_tempFile->open(); disconnect(&m_fileWatch, &KDirWatch::dirty, this, &MaximaExpression::imageChanged); m_fileWatch.addFile(m_tempFile->fileName()); connect(&m_fileWatch, &KDirWatch::dirty, this, &MaximaExpression::imageChanged); } const QString& cmd=command(); bool isComment = true; int commentLevel = 0; bool inString = false; for (int i = 0; i < cmd.size(); ++i) { if (cmd[i] == QLatin1Char('\\')) { ++i; // skip the next character if (commentLevel == 0 && !inString) { isComment = false; } } else if (cmd[i] == QLatin1Char('"') && commentLevel == 0) { inString = !inString; isComment = false; } else if (cmd.mid(i,2) == QLatin1String("/*") && !inString) { ++commentLevel; ++i; } else if (cmd.mid(i,2) == QLatin1String("*/") && !inString) { if (commentLevel == 0) { qDebug() << "Comments mismatched!"; setErrorMessage(i18n("Error: Too many */")); setStatus(Cantor::Expression::Error); return; } ++i; --commentLevel; } else if (isComment && commentLevel == 0 && !cmd[i].isSpace()) { isComment = false; } } if (commentLevel > 0) { qDebug() << "Comments mismatched!"; setErrorMessage(i18n("Error: Too many /*")); setStatus(Cantor::Expression::Error); return; } if (inString) { qDebug() << "String not closed"; setErrorMessage(i18n("Error: expected \" before ;")); setStatus(Cantor::Expression::Error); return; } if(isComment) { setStatus(Cantor::Expression::Done); return; } session()->enqueueExpression(this); } void MaximaExpression::interrupt() { qDebug()<<"interrupting"; setStatus(Cantor::Expression::Interrupted); } QString MaximaExpression::internalCommand() { QString cmd=command(); if(m_isPlot) { if(!m_tempFile) { qDebug()<<"plotting without tempFile"; return QString(); } QString fileName = m_tempFile->fileName(); #ifdef WITH_EPS const QString psParam=QLatin1String("[gnuplot_ps_term_command, \"set size 1.0, 1.0; set term postscript eps color solid \"]"); const QString plotParameters = QLatin1String("[ps_file, \"")+ fileName+QLatin1String("\"],")+psParam; #else const QString plotParameters = QLatin1String("[gnuplot_term, \"png size 500,340\"], [gnuplot_out_file, \"")+fileName+QLatin1String("\"]"); #endif cmd.replace(QRegExp(QLatin1String("((plot2d|plot3d|contour_plot)\\s*\\(.*)\\)([;\n]|$)")), QLatin1String("\\1, ")+plotParameters+QLatin1String(");")); } if (!cmd.endsWith(QLatin1Char('$'))) { if (!cmd.endsWith(QLatin1String(";"))) cmd+=QLatin1Char(';'); } //replace all newlines with spaces, as maxima isn't sensitive about //whitespaces, and without newlines the whole command //is executed at once, without outputting an input //prompt after each line cmd.replace(QLatin1Char('\n'), QLatin1Char(' ')); //lisp-quiet doesn't print a prompt after the command //is completed, which causes the parsing to hang. //replace the command with the non-quiet version cmd.replace(QRegExp(QLatin1String("^:lisp-quiet")), QLatin1String(":lisp")); return cmd; } void MaximaExpression::forceDone() { qDebug()<<"forcing Expression state to DONE"; setResult(nullptr); setStatus(Cantor::Expression::Done); } void MaximaExpression::addInformation(const QString& information) { qDebug()<<"adding information"; QString inf=information; if(!inf.endsWith(QLatin1Char(';'))) inf+=QLatin1Char(';'); Cantor::Expression::addInformation(inf); dynamic_cast(session())->sendInputToProcess(inf+QLatin1Char('\n')); } //The maxima backend is modified, so that it outputs //xml-style tags around outputs, input prompts etc. //the following are some simple helper functions to faciliate parsing inline void skipWhitespaces(int* idx, const QString& txt) { for(;*idx < txt.size() && (txt[*idx]).isSpace();++(*idx)); } QStringRef readXmlOpeningTag(int* idx, const QString& txt, bool* isComplete=nullptr) { qDebug()<<"trying to read an opening tag"; if (*idx >= txt.size()) return QStringRef(); skipWhitespaces(idx, txt); if(isComplete) *isComplete=false; if(txt[*idx]!=QLatin1Char('<')) { qDebug()<<"This is NOT AN OPENING TAG."<')) { if(isComplete) *isComplete=true; break; }else length++; } return QStringRef(&txt, startIndex, length); } QStringRef readXmlTagContent(int* idx, const QString& txt, const QStringRef& name, bool* isComplete=nullptr) { bool readingClosingTag=false; int contentStartIdx=*idx; int contentLength=0; int currentTagStartIdx=-1; int currentTagLength=0; if(isComplete) *isComplete=false; while(*idx0&&txt[(*idx)-1]==QLatin1Char('<')) { //remove the opening < contentLength--; currentTagStartIdx=*idx+1; currentTagLength=0; readingClosingTag=true; } else if(readingClosingTag) { if(c==QLatin1Char('>')) { const QStringRef currentTagName(&txt, currentTagStartIdx, currentTagLength); if(currentTagName==name) { //eat up the closing > ++(*idx); if(isComplete) (*isComplete)=true; break; } readingClosingTag=false; }else currentTagLength++; } else contentLength++; ++(*idx); } if(contentStartIdx+contentLength>txt.size()) { qDebug()<<"something is wrong with the content-length "<"), idx); int idx2=out.indexOf(QLatin1String(""), idx); idx1=(idx1==-1) ? out.size():idx1; idx2=(idx2==-1) ? out.size():idx2; int newIdx=qMin(idx1, idx2); if(newIdx>idx) { const QString& err=out.mid(idx, newIdx-idx); if(!err.isEmpty()) m_gotErrorContent=true; errorBuffer+=err; qDebug()<<"the unmatched part of the output is: "<0) { textBuffer.append(QLatin1String("\n")); latexBuffer.append(QLatin1String("\n")); } result=parseResult(&idx, out, textBuffer, latexBuffer); numResults++; qDebug()<<"got "<type()==Cantor::TextResult::Type) m_errorBuffer.prepend(dynamic_cast(result)->plain()+QLatin1String("\n")); else if(result->type()==Cantor::LatexResult::Type) m_errorBuffer.prepend(dynamic_cast(result)->plain()+QLatin1String("\n")); } Cantor::TextResult* result=new Cantor::TextResult(m_errorBuffer); setResult(result); setStatus(Cantor::Expression::Done); }else if(m_isHelpRequest) //Help Messages are also provided in the errorBuffer. { Cantor::HelpResult* result=new Cantor::HelpResult(m_errorBuffer); setResult(result); setStatus(Cantor::Expression::Done); }else { if(result) { qDebug()<<"result: "<toHtml(); if(result->type()==Cantor::TextResult::Type) m_errorBuffer.prepend(dynamic_cast(result)->plain()+QLatin1String("\n")); else if(result->type()==Cantor::LatexResult::Type) m_errorBuffer.prepend(dynamic_cast(result)->plain()+QLatin1String("\n")); } qDebug()<<"errorBuffer: "< element wasn't read completely, there //is no point in trying to render it. Use text for //incomplete results. if(!isLatexComplete ||(latexBuffer.trimmed().isEmpty()&&latex.isEmpty()) ||m_isHelpRequest||isInternal()) { qDebug()<<"using text"; result=new Cantor::TextResult(textBuffer); }else { qDebug()<<"using latex"; //strip away the latex code for the label. //it is contained in an \mbox{} call int i; int pcount=0; for(i=latex.indexOf(QLatin1String("\\mbox{"))+5;isetFormat(Cantor::TextResult::LatexFormat); } } return result; } /*! example output for the simple expression '5+5': latex mode - "\n(%o1) 10\n\\mbox{\\tt\\red(\\mathrm{\\%o1}) \\black}10\n(%i2) \n" text mode - "\n(%o1) 10\n\n(%i2) \n" */ bool MaximaExpression::parseOutput(QString& out) { const int promptStart = out.indexOf(QLatin1String("")); const int promptEnd = out.indexOf(QLatin1String("")); const QString prompt = out.mid(promptStart + 15, promptEnd - promptStart - 15).simplified(); //check whether the result is part of the promt - this is the case when additional input is required from the user if (prompt.contains(QLatin1String(""))) { //text part of the output const int textContentStart = prompt.indexOf(QLatin1String("")); const int textContentEnd = prompt.indexOf(QLatin1String("")); QString textContent = prompt.mid(textContentStart + 13, textContentEnd - textContentStart - 13).trimmed(); qDebug()<<"asking for additional input for " << textContent; emit needsAdditionalInformation(textContent); return true; } qDebug()<<"new input label: " << prompt; QString errorContent; //parse the results int resultStart = out.indexOf(QLatin1String("")); if (resultStart != -1) errorContent += out.mid(0, resultStart); while (resultStart != -1) { int resultEnd = out.indexOf(QLatin1String(""), resultStart + 15); const QString resultContent = out.mid(resultStart + 15, resultEnd - resultStart - 15); parseResult(resultContent); //search for the next openning tag after the current closing tag resultStart = out.indexOf(QLatin1String(""), resultEnd + 16); } //parse the error message, the part outside of the tags int lastResultEnd = out.lastIndexOf(QLatin1String("")); if (lastResultEnd != -1) lastResultEnd += 16; else lastResultEnd = 0; errorContent += out.mid(lastResultEnd, promptStart - lastResultEnd).trimmed(); if (errorContent.isEmpty()) { setStatus(Cantor::Expression::Done); } else { qDebug() << "error content: " << errorContent; if (out.contains(QLatin1String("cantor-value-separator"))) { //when fetching variables, in addition to the actual result with variable names and values, //Maxima also write out the names of the variables to the error buffer. //we don't interpret this as an error. setStatus(Cantor::Expression::Done); } else if(m_isHelpRequest) //help messages are also part of the error output { Cantor::HelpResult* result = new Cantor::HelpResult(errorContent); addResult(result); setStatus(Cantor::Expression::Done); } else { errorContent = errorContent.replace(QLatin1String("\n\n"), QLatin1String("
")); errorContent = errorContent.replace(QLatin1String("\n"), QLatin1String("
")); setErrorMessage(errorContent); setStatus(Cantor::Expression::Error); } } return true; } void MaximaExpression::parseResult(const QString& resultContent) { qDebug()<<"result content: " << resultContent; //text part of the output const int textContentStart = resultContent.indexOf(QLatin1String("")); const int textContentEnd = resultContent.indexOf(QLatin1String("")); QString textContent = resultContent.mid(textContentStart + 13, textContentEnd - textContentStart - 13).trimmed(); qDebug()<<"text content: " << textContent; //output label can be a part of the text content -> determine it const QRegExp regex = QRegExp(MaximaSession::MaximaOutputPrompt.pattern()); const int index = regex.indexIn(textContent); QString outputLabel; if (index != -1) // No match, so output don't contain output label outputLabel = textContent.mid(index, regex.matchedLength()).trimmed(); qDebug()<<"output label: " << outputLabel; //extract the expression id bool ok; QString idString = outputLabel.mid(3, outputLabel.length()-4); int id = idString.toInt(&ok); ok ? setId(id) : setId(-1); qDebug()<<"expression id: " << this->id(); //remove the output label from the text content textContent = textContent.remove(outputLabel).trimmed(); //determine the actual result Cantor::TextResult* result = nullptr; const int latexContentStart = resultContent.indexOf(QLatin1String("")); if (latexContentStart != -1) { //latex output is available const int latexContentEnd = resultContent.indexOf(QLatin1String("")); QString latexContent = resultContent.mid(latexContentStart + 14, latexContentEnd - latexContentStart - 14).trimmed(); qDebug()<<"latex content: " << latexContent; //replace the \mbox{} environment, if available, by the eqnarray environment if (latexContent.indexOf(QLatin1String("\\mbox{")) != -1) { int i; int pcount=0; for(i = latexContent.indexOf(QLatin1String("\\mbox{"))+5; i < latexContent.size(); ++i) { if(latexContent[i]==QLatin1Char('{')) pcount++; else if(latexContent[i]==QLatin1Char('}')) pcount--; if(pcount==0) break; } QString modifiedLatexContent = latexContent.mid(i+1); if(modifiedLatexContent.trimmed().isEmpty()) { //empty content in the \mbox{} environment (e.g. for print() outputs), use the latex string outside of the \mbox{} environment modifiedLatexContent = latexContent.left(latexContent.indexOf(QLatin1String("\\mbox{"))); } modifiedLatexContent.prepend(QLatin1String("\\begin{eqnarray*}")); modifiedLatexContent.append(QLatin1String("\\end{eqnarray*}")); result = new Cantor::TextResult(modifiedLatexContent, textContent); qDebug()<<"modified latex content: " << modifiedLatexContent; } else { //no \mbox{} available, use what we've got. result = new Cantor::TextResult(latexContent, textContent); } result->setFormat(Cantor::TextResult::LatexFormat); } else { //no latex output is availabe, the actual result is part of the textContent string result = new Cantor::TextResult(textContent); } addResult(result); } void MaximaExpression::parseError(const QString& out) { m_errorBuffer.append(out); } void MaximaExpression::imageChanged() { qDebug()<<"the temp image has changed"; if(m_tempFile->size()>0) { #ifdef WITH_EPS setResult( new Cantor::EpsResult( QUrl::fromLocalFile(m_tempFile->fileName()) ) ); #else setResult( new Cantor::ImageResult( QUrl::fromLocalFile(m_tempFile->fileName()) ) ); #endif setStatus(Cantor::Expression::Done); } } diff --git a/src/backends/maxima/maximaexpression.h b/src/backends/maxima/maximaexpression.h index 8f564c84..fac33329 100644 --- a/src/backends/maxima/maximaexpression.h +++ b/src/backends/maxima/maximaexpression.h @@ -1,72 +1,72 @@ /* 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) 2009-2012 Alexander Rieder */ #ifndef _MAXIMAEXPRESSION_H #define _MAXIMAEXPRESSION_H #include "expression.h" #include "KDirWatch" #include class QTemporaryFile; class MaximaExpression : public Cantor::Expression { Q_OBJECT public: - explicit MaximaExpression(Cantor::Session*); + explicit MaximaExpression(Cantor::Session*, bool internal = false); void evaluate() override; void interrupt() override; void addInformation(const QString&) override; bool needsLatexResult(); //returns the command that should be send to //the Maxima process, it's different from the //command() for example to allow plot embedding QString internalCommand(); //Forces the status of this Expression to done void forceDone(); //reads from @param out until a prompt indicates that a new expression has started bool parseOutput(QString&); bool parseOutputOld(QString&); void parseError(const QString&); private Q_SLOTS: void imageChanged(); private: void parseResult(const QString&); Cantor::Result* parseResult(int* idx,QString& out,QString& textBuffer,QString& latexBuffer); QTemporaryFile *m_tempFile; KDirWatch m_fileWatch; bool m_isHelpRequest; bool m_isPlot; QString m_errorBuffer; bool m_gotErrorContent; }; #endif /* _MAXIMAEXPRESSION_H */ diff --git a/src/backends/maxima/maximasession.cpp b/src/backends/maxima/maximasession.cpp index d78d8309..97841919 100644 --- a/src/backends/maxima/maximasession.cpp +++ b/src/backends/maxima/maximasession.cpp @@ -1,347 +1,345 @@ /* 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) 2009-2012 Alexander Rieder Copyright (C) 2017-2018 Alexander Semke (alexander.semke@web.de) */ #include "maximasession.h" #include "maximaexpression.h" #include "maximacompletionobject.h" #include "maximasyntaxhelpobject.h" #include "maximahighlighter.h" #include "maximavariablemodel.h" #include "result.h" #include "settings.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif //NOTE: the \\s in the expressions is needed, because Maxima seems to sometimes insert newlines/spaces between the letters //maybe this is caused by some behaviour if the Prompt is split into multiple "readStdout" calls //the Expressions are encapsulated in () to allow capturing for the text const QRegExp MaximaSession::MaximaOutputPrompt=QRegExp(QLatin1String("(\\(\\s*%\\s*o\\s*[0-9\\s]*\\))")); //Text, maxima outputs, before any output MaximaSession::MaximaSession( Cantor::Backend* backend ) : Session(backend), m_process(nullptr), m_variableModel(new MaximaVariableModel(this)), m_justRestarted(false) { } void MaximaSession::login() { qDebug()<<"login"; if (m_process) return; //TODO: why do we call login() again?!? emit loginStarted(); QStringList arguments; arguments << QLatin1String("--quiet"); //Suppress Maxima start-up message const QString initFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/maximabackend/cantor-initmaxima.lisp")); arguments << QLatin1String("--init-lisp=") + initFile; //Set the name of the Lisp initialization file m_process = new QProcess(this); m_process->start(MaximaSettings::self()->path().toLocalFile(), arguments); m_process->waitForStarted(); m_process->waitForReadyRead(); qDebug()<readAllStandardOutput(); connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(restartMaxima())); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStdOut())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); //TODO // if(!MaximaSettings::self()->autorunScripts().isEmpty()){ // QString autorunScripts = MaximaSettings::self()->autorunScripts().join(QLatin1String("\n")); // evaluateExpression(autorunScripts, MaximaExpression::DeleteOnFinish); // // runFirstExpression(); // } emit loginDone(); qDebug()<<"login done"; } void MaximaSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, nullptr, this, nullptr); // if(status()==Cantor::Session::Running) //TODO: terminate the running expressions first write(QLatin1String("quit();\n")); qDebug()<<"waiting for maxima to finish"; m_process->waitForFinished(); qDebug()<<"maxima exit finished"; if(m_process->state() != QProcess::NotRunning) { m_process->kill(); qDebug()<<"maxima still running, process kill enforced"; } expressionQueue().clear(); delete m_process; m_process = nullptr; qDebug()<<"logout done"; } -Cantor::Expression* MaximaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) +Cantor::Expression* MaximaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; - MaximaExpression* expr = new MaximaExpression(this); + MaximaExpression* expr = new MaximaExpression(this, internal); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void MaximaSession::readStdErr() { qDebug()<<"reading stdErr"; if (!m_process) return; QString out=QLatin1String(m_process->readAllStandardError()); if(expressionQueue().size()>0) { MaximaExpression* expr = static_cast(expressionQueue().first()); expr->parseError(out); } } void MaximaSession::readStdOut() { QString out = QLatin1String(m_process->readAllStandardOutput()); m_cache += out; //collect the multi-line output until Maxima has finished the calculation and returns a new promt if ( !out.contains(QLatin1String("")) ) return; if(expressionQueue().isEmpty()) { //queue is empty, interrupt was called, nothing to do here qDebug()<(expressionQueue().first()); if (!expr) return; //should never happen qDebug()<<"output: " << m_cache; expr->parseOutput(m_cache); m_cache.clear(); } void MaximaSession::killLabels() { - Cantor::Expression* e=evaluateExpression(QLatin1String("kill(labels);"), Cantor::Expression::DeleteOnFinish); - e->setInternal(true); + Cantor::Expression* e=evaluateExpression(QLatin1String("kill(labels);"), Cantor::Expression::DeleteOnFinish, true); //TODO: what for? connect(e, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SIGNAL(ready())); } void MaximaSession::reportProcessError(QProcess::ProcessError e) { qDebug()<<"process error"<command())&&!exp2.exactMatch(expression->command())) { m_variableModel->checkForNewFunctions(); m_variableModel->checkForNewVariables(); }else { changeStatus(Cantor::Session::Done); } }else { runFirstExpression(); } } } void MaximaSession::runFirstExpression() { qDebug()<<"running next expression"; if (!m_process) return; if(!expressionQueue().isEmpty()) { MaximaExpression* expr = static_cast(expressionQueue().first()); QString command=expr->internalCommand(); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); expr->setStatus(Cantor::Expression::Computing); if(command.isEmpty()) { qDebug()<<"empty command"; expr->forceDone(); } else { m_cache.clear(); write(command + QLatin1Char('\n')); } } } void MaximaSession::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 } expressionQueue().first()->interrupt(); expressionQueue().removeFirst(); foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Done); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); m_cache.clear(); } void MaximaSession::sendInputToProcess(const QString& input) { write(input); } void MaximaSession::restartMaxima() { qDebug()<<"restarting maxima cooldown: "<setInternal(true); + Cantor::Expression* exp=evaluateExpression(QString::fromLatin1(":lisp(setf $display2d %1)").arg(val), Cantor::Expression::DeleteOnFinish, true); Cantor::Session::setTypesettingEnabled(enable); } Cantor::CompletionObject* MaximaSession::completionFor(const QString& command, int index) { return new MaximaCompletionObject(command, index, this); } Cantor::SyntaxHelpObject* MaximaSession::syntaxHelpFor(const QString& command) { return new MaximaSyntaxHelpObject(command, this); } QSyntaxHighlighter* MaximaSession::syntaxHighlighter(QObject* parent) { return new MaximaHighlighter(parent, this); } QAbstractItemModel* MaximaSession::variableModel() { return m_variableModel; } void MaximaSession::write(const QString& exp) { qDebug()<<"################################## EXPRESSION START ###############################################"; qDebug()<<"sending expression to maxima process: " << exp; m_process->write(exp.toUtf8()); } diff --git a/src/backends/maxima/maximasession.h b/src/backends/maxima/maximasession.h index 91ac10dc..e4ac7580 100644 --- a/src/backends/maxima/maximasession.h +++ b/src/backends/maxima/maximasession.h @@ -1,83 +1,83 @@ /* Tims 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) 2009-2012 Alexander Rieder */ #ifndef _MAXIMASESSION_H #define _MAXIMASESSION_H #include "session.h" #include "expression.h" #include class MaximaExpression; class MaximaVariableModel; #ifdef Q_OS_WIN class KProcess; #else class KPtyProcess; #endif class MaximaSession : public Cantor::Session { Q_OBJECT public: static const QRegExp MaximaOutputPrompt; MaximaSession( Cantor::Backend* backend); void login() override; void logout() override; - Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave) override; + Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; void interrupt() override; void sendInputToProcess(const QString& input); void setTypesettingEnabled(bool enable) override; Cantor::CompletionObject* completionFor(const QString& command, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& command) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; QAbstractItemModel* variableModel() override; void runFirstExpression() override; public Q_SLOTS: void readStdOut(); void readStdErr(); private Q_SLOTS: void currentExpressionChangedStatus(Cantor::Expression::Status status); void restartMaxima(); void restartsCooledDown(); void killLabels(); void reportProcessError(QProcess::ProcessError error); private: void write(const QString&); QProcess* m_process; QString m_cache; MaximaVariableModel* m_variableModel; bool m_justRestarted; }; #endif /* _MAXIMASESSION_H */ diff --git a/src/backends/maxima/maximavariablemodel.cpp b/src/backends/maxima/maximavariablemodel.cpp index a806434f..580821d1 100644 --- a/src/backends/maxima/maximavariablemodel.cpp +++ b/src/backends/maxima/maximavariablemodel.cpp @@ -1,271 +1,269 @@ /* 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 Alexander Rieder */ #include "maximavariablemodel.h" #include "maximasession.h" #include "maximaexpression.h" #include "textresult.h" #include "latexresult.h" #include #include //command used to inspect a maxima variable. %1 is the name of that variable const QString MaximaVariableModel::inspectCommand=QLatin1String(":lisp($disp $%1)"); const QString MaximaVariableModel::variableInspectCommand=QLatin1String(":lisp(cantor-inspect $%1)"); MaximaVariableModel::MaximaVariableModel( MaximaSession* session) : Cantor::DefaultVariableModel(session) { } void MaximaVariableModel::checkForNewVariables() { qDebug()<<"checking for new variables"; const QString& cmd=variableInspectCommand.arg(QLatin1String("values")); - Cantor::Expression* expr=session()->evaluateExpression(cmd); - expr->setInternal(true); + Cantor::Expression* expr=session()->evaluateExpression(cmd, Cantor::Expression::FinishingBehavior::DoNotDelete, true); connect(expr, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewVariables); } void MaximaVariableModel::checkForNewFunctions() { qDebug()<<"checking for new functions"; const QString& cmd=inspectCommand.arg(QLatin1String("functions")); - Cantor::Expression* expr=session()->evaluateExpression(cmd); - expr->setInternal(true); + Cantor::Expression* expr=session()->evaluateExpression(cmd, Cantor::Expression::FinishingBehavior::DoNotDelete, true); connect(expr, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewFunctions); } QList parse(MaximaExpression* expr) { if(!expr || expr->status()!=Cantor::Expression::Done || expr->results().isEmpty()) { return QList(); } //for parsing of names and values below (old code) we need to combine multiple results back to one string QString text; for (auto* result : expr->results()) { if(result->type()==Cantor::TextResult::Type) text += dynamic_cast(result)->plain(); else if(expr->result()->type()==Cantor::LatexResult::Type) text += dynamic_cast(result)->plain(); } const int nameIndex=text.indexOf(QLatin1Char(']')); QString namesString=text.left(nameIndex); //namesString.chop(1); namesString=namesString.mid(1); namesString=namesString.trimmed(); qDebug()<<"variable names: "<(); QStringList variableNames; QString valuesString; bool hasValues = false; QStringList variableValues; if ( namesString.contains(QLatin1Char(')')) ) { //function definition(s): e.g //text = "[f1(x),f2(x,y),f3(x,y,z)]\n$DONE" //nameString = f1(x),f2(x,y),f3(x,y,z) //variableString = "\n$DONE" variableNames = namesString.split(QLatin1String("),")); } else { //variable definition(s): e.g. //text = "[a,b]\n1\n\"-cantor-value-separator-\"\n2\n\"-cantor-value-separator-\"\n($A $B)" //nameString = "[a,b]" //variableString = "\n1\n\"-cantor-value-separator-\"\n2\n\"-cantor-value-separator-\"\n($A $B)" variableNames = namesString.split(QLatin1Char(',')); valuesString = text.mid(nameIndex+1).trimmed(); valuesString = valuesString.remove(QLatin1String("\n")); //lists with many elements have line breaks, remove them variableValues = valuesString.split(QLatin1String("\"-cantor-value-separator-\"")); hasValues = variableValues.isEmpty(); } qDebug()< variables; variables.reserve(variableNames.size()); for(int i=0;ii) { var.value=variableValues.at(i).trimmed(); var.value=var.value.remove(QLatin1String("\n")); //lists with many elements have line breaks, remove them } else var.value=QLatin1String("unknown"); variables<(sender()); QList newVars=parse(expr); QStringList addedVars; QStringList removedVars; //remove the old variables for (const Variable& var : m_variables) { //check if this var is present in the new variables bool found=false; for (const Variable& var2 : newVars) { if(var.name==var2.name) { found=true; break; } } if(!found) { removeVariable(var); removedVars<deleteLater(); emit variablesAdded(addedVars); emit variablesRemoved(removedVars); } void MaximaVariableModel::parseNewFunctions(Cantor::Expression::Status status) { if (status != Cantor::Expression::Done && status != Cantor::Expression::Error) return; qDebug()<<"parsing functions"; MaximaExpression* expr=dynamic_cast(sender()); QList newVars=parse(expr); QStringList addedVars; QStringList removedVars; //remove the old variables for (const Variable& var : m_functions) { //check if this var is present in the new variables bool found=false; for (const Variable& var2 : newVars) { if(var.name==var2.name) { found=true; break; } } if(!found) { removeVariable(var); removedVars<deleteLater(); emit functionsAdded(addedVars); emit functionsRemoved(removedVars); } MaximaSession* MaximaVariableModel::maximaSession() { return static_cast (session()); } QList MaximaVariableModel::variables() { return m_variables; } QList MaximaVariableModel::functions() { return m_functions; } QStringList MaximaVariableModel::variableNames() { QStringList names; for (const Cantor::DefaultVariableModel::Variable& var : m_variables) names< */ #include "nullexpression.h" #include "textresult.h" #include "imageresult.h" #include "helpresult.h" #include #include #include -NullExpression::NullExpression( Cantor::Session* session ) : Cantor::Expression(session) +NullExpression::NullExpression( Cantor::Session* session, bool internal ) : Cantor::Expression(session, internal) { m_timer=new QTimer(this); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &NullExpression::evalFinished); } void NullExpression::evaluate() { qDebug()<<"evaluating "<start(1000); } void NullExpression::interrupt() { qDebug()<<"interruptinging command"; m_timer->stop(); setStatus(Cantor::Expression::Interrupted); } void NullExpression::evalFinished() { qDebug()<<"evaluation finished"; if ( command()==QLatin1String("img") ) setResult( new Cantor::ImageResult( QUrl::fromLocalFile(KIconLoader::global()->iconPath(QLatin1String("kde"), KIconLoader::Desktop)), QLatin1String("alternative") ) ); else setResult(new Cantor::TextResult(QLatin1String("result: ")+command())); setStatus(Cantor::Expression::Done); } diff --git a/src/backends/null/nullexpression.h b/src/backends/null/nullexpression.h index 864f6a10..c68d0158 100644 --- a/src/backends/null/nullexpression.h +++ b/src/backends/null/nullexpression.h @@ -1,45 +1,45 @@ /* 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) 2009 Alexander Rieder */ #ifndef _NULLEXPRESSION_H #define _NULLEXPRESSION_H #include "expression.h" class QTimer; class NullExpression : public Cantor::Expression { Q_OBJECT public: - NullExpression( Cantor::Session* session); + NullExpression( Cantor::Session* session, bool internal = false); ~NullExpression() override = default; void evaluate() override; void interrupt() override; public Q_SLOTS: void evalFinished(); private: QTimer* m_timer; }; #endif /* _NULLEXPRESSION_H */ diff --git a/src/backends/null/nullsession.cpp b/src/backends/null/nullsession.cpp index 43839ccb..bd485d19 100644 --- a/src/backends/null/nullsession.cpp +++ b/src/backends/null/nullsession.cpp @@ -1,86 +1,86 @@ /* 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) 2009 Alexander Rieder */ #include "nullsession.h" #include "nullexpression.h" #include "nullcompletionobject.h" #include NullSession::NullSession( Cantor::Backend* backend) : Session(backend) { } void NullSession::login() { qDebug()<<"login"; emit loginStarted(); changeStatus(Cantor::Session::Done); emit loginDone(); } void NullSession::logout() { qDebug()<<"logout"; } void NullSession::interrupt() { qDebug()<<"interrupt"; foreach(Cantor::Expression* e, m_runningExpressions) e->interrupt(); m_runningExpressions.clear(); changeStatus(Cantor::Session::Done); } -Cantor::Expression* NullSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) +Cantor::Expression* NullSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug()<<"evaluating: "<setFinishingBehavior(behave); connect(expr, &NullExpression::statusChanged, this, &NullSession::expressionFinished); expr->setCommand(cmd); expr->evaluate(); if(m_runningExpressions.isEmpty()) changeStatus(Cantor::Session::Running); m_runningExpressions.append(expr); return expr; } Cantor::CompletionObject* NullSession::completionFor(const QString& command, int index) { qDebug()<<"tab completion for "<(sender()); m_runningExpressions.removeAll(expression); qDebug()<<"size: "< */ #ifndef _NULLSESSION_H #define _NULLSESSION_H #include "session.h" class NullExpression; class NullSession : public Cantor::Session { Q_OBJECT public: NullSession( Cantor::Backend* backend); ~NullSession() override = default; void login() override; void logout() override; void interrupt() override; - Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave) override; + Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; private Q_SLOTS: void expressionFinished(); private: QList m_runningExpressions; }; #endif /* _NULLSESSION_H */ diff --git a/src/backends/octave/octaveexpression.cpp b/src/backends/octave/octaveexpression.cpp index d3bbed95..ac2afccd 100644 --- a/src/backends/octave/octaveexpression.cpp +++ b/src/backends/octave/octaveexpression.cpp @@ -1,185 +1,185 @@ /* Copyright (C) 2010 Miha Čančula 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. */ #include "octaveexpression.h" #include "octavesession.h" #include "defaultvariablemodel.h" #include "textresult.h" #include #include #include static const char* printCommand = "cantor_print();"; -OctaveExpression::OctaveExpression(Cantor::Session* session): Expression(session), +OctaveExpression::OctaveExpression(Cantor::Session* session, bool internal): Expression(session, internal), m_plotPending(false), m_finished(false), m_error(false), m_appendPlotCommand(false), m_appendDot(false) { m_plotCommands << QLatin1String("plot") << QLatin1String("semilogx") << QLatin1String("semilogy") << QLatin1String("loglog") << QLatin1String("polar") << QLatin1String("contour") << QLatin1String("bar") << QLatin1String("stairs") << QLatin1String("errorbar") << QLatin1String("sombrero") << QLatin1String("hist") << QLatin1String("fplot") << QLatin1String("imshow") << QLatin1String("stem") << QLatin1String("stem3") << QLatin1String("scatter") << QLatin1String("pareto") << QLatin1String("rose") << QLatin1String("pie") << QLatin1String("quiver") << QLatin1String("compass") << QLatin1String("feather") << QLatin1String("pcolor") << QLatin1String("area") << QLatin1String("fill") << QLatin1String("comet") << QLatin1String("plotmatrix") /* 3d-plots */ << QLatin1String("plot3") << QLatin1String("mesh") << QLatin1String("meshc") << QLatin1String("meshz") << QLatin1String("surf") << QLatin1String("surfc") << QLatin1String("surfl") << QLatin1String("surfnorm") << QLatin1String("isosurface")<< QLatin1String("isonormals") << QLatin1String("isocaps") /* 3d-plots defined by a function */ << QLatin1String("ezplot3") << QLatin1String("ezmesh") << QLatin1String("ezmeshc") << QLatin1String("ezsurf") << QLatin1String("ezsurfc"); m_plotCommands << QLatin1String("cantor_plot2d") << QLatin1String("cantor_plot3d"); } void OctaveExpression::interrupt() { qDebug() << "interrupt"; setStatus(Interrupted); } void OctaveExpression::evaluate() { qDebug() << "evaluate"; QString cmd = command(); QStringList cmdWords = cmd.split(QRegExp(QLatin1String("\\b")), QString::SkipEmptyParts); if (!cmdWords.contains(QLatin1String("help")) && !cmdWords.contains(QLatin1String("completion_matches"))) { foreach (const QString& plotCmd, m_plotCommands) { if (cmdWords.contains(plotCmd)) { setPlotPending(true); qDebug() << "Executing a plot command"; break; } } } if ( m_plotPending && !cmd.contains(QLatin1String("cantor_plot")) && !cmd.contains(QLatin1String(printCommand))) { // This was a manual plot, we have to add a print command if (!cmd.endsWith(QLatin1Char(';')) && !cmd.endsWith(QLatin1Char(','))) { cmd += QLatin1Char(','); m_appendDot = true; } cmd += QLatin1String(printCommand); setCommand(cmd); m_appendPlotCommand = true; } m_finished = false; session()->enqueueExpression(this); } void OctaveExpression::parseOutput(const QString& output) { qDebug() << "parseOutput: " << output; m_resultString += output; } void OctaveExpression::parseError(const QString& error) { qDebug() << error; m_error = true; setErrorMessage(error); } void OctaveExpression::parsePlotFile(const QString& file) { qDebug() << "parsePlotFile"; if (QFile::exists(file)) { qDebug() << "OctaveExpression::parsePlotFile: " << file; setResult(new OctavePlotResult(QUrl::fromLocalFile(file))); setPlotPending(false); if (m_finished) { setStatus(Done); } } } void OctaveExpression::finalize() { qDebug() << "finalize: " << m_resultString; if (m_appendPlotCommand) { QString cmd = command(); cmd.remove(cmd.length()-strlen(printCommand),strlen(printCommand)); m_appendPlotCommand = false; if (m_appendDot) { cmd.remove(cmd.length()-1,1); m_appendDot = false; } setCommand(cmd); } if (!m_error && !m_resultString.trimmed().isEmpty()) { if (command().contains(QLatin1String("help"))) { setResult(new Cantor::HelpResult(m_resultString)); } else { setResult(new Cantor::TextResult(m_resultString)); } } foreach ( const QString& line, m_resultString.simplified().split(QLatin1Char('\n'), QString::SkipEmptyParts) ) { if ((m_resultString.contains(QLatin1Char('='))) && !(command().startsWith(QLatin1String("help("))) && !(command().contains(QLatin1String("help "))) && !(command().contains(QLatin1String("type(")))) { qDebug() << line; // Probably a new variable QStringList parts = line.split(QLatin1Char('=')); if (parts.size() >= 2) { Cantor::DefaultVariableModel* model = dynamic_cast(session()->variableModel()); if (model) { model->addVariable(parts.first().trimmed(), parts.last().trimmed()); } } } } qDebug() << m_plotPending << m_error; m_finished = true; if (!m_plotPending) { setStatus(m_error ? Error : Done); } } void OctaveExpression::setPlotPending(bool plot) { m_plotPending = plot; } diff --git a/src/backends/octave/octaveexpression.h b/src/backends/octave/octaveexpression.h index 9182441e..eeba9b0a 100644 --- a/src/backends/octave/octaveexpression.h +++ b/src/backends/octave/octaveexpression.h @@ -1,63 +1,63 @@ /* Copyright (C) 2010 Miha Čančula 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. */ #ifndef OCTAVEEXPRESSION_H #define OCTAVEEXPRESSION_H #include #include #include #ifdef WITH_EPS #include "epsresult.h" using OctavePlotResult = Cantor::EpsResult; #else #include "imageresult.h" typedef Cantor::ImageResult OctavePlotResult; #endif class OctaveExpression : public Cantor::Expression { Q_OBJECT public: - OctaveExpression(Cantor::Session*); + OctaveExpression(Cantor::Session*, bool internal = false); void interrupt() override; void evaluate() override; void parseOutput(const QString&); void parseError(const QString&); void parsePlotFile(const QString&); void finalize(); void setPlotPending(bool); private: QString m_resultString; bool m_plotPending; bool m_finished; bool m_error; bool m_appendPlotCommand; bool m_appendDot; QStringList m_plotCommands; }; #endif // OCTAVEEXPRESSION_H diff --git a/src/backends/octave/octavesession.cpp b/src/backends/octave/octavesession.cpp index b272beb7..4393efac 100644 --- a/src/backends/octave/octavesession.cpp +++ b/src/backends/octave/octavesession.cpp @@ -1,342 +1,342 @@ /* Copyright (C) 2010 Miha Čančula 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. */ #include "octavesession.h" #include "octaveexpression.h" #include "octavecompletionobject.h" #include "octavesyntaxhelpobject.h" #include "result.h" #include "textresult.h" #include "settings.h" #include "octave-backend-config.h" #include #include #include #include #include "octavehighlighter.h" #include #ifndef Q_OS_WIN #include #endif #include OctaveSession::OctaveSession ( Cantor::Backend* backend ) : Session ( backend ), m_process(nullptr), m_watch(nullptr), m_variableModel(new Cantor::DefaultVariableModel(this)) { qDebug() << octaveScriptInstallDir; } void OctaveSession::login() { qDebug() << "login"; emit loginStarted(); m_process = new KProcess ( this ); QStringList args; args << QLatin1String("--silent"); args << QLatin1String("--interactive"); args << QLatin1String("--persist"); // Add the cantor script directory to search path args << QLatin1String("--eval"); args << QString::fromLatin1("addpath %1;").arg(octaveScriptInstallDir); if (OctaveSettings::integratePlots()) { // Do not show the popup when plotting, rather only print to a file args << QLatin1String("--eval"); args << QLatin1String("graphics_toolkit gnuplot;"); args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"off\");"); } else { args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"on\");"); } // Do not show extra text in help commands args << QLatin1String("--eval"); args << QLatin1String("suppress_verbose_help_message(1);"); // Print the temp dir, used for plot files args << QLatin1String("--eval"); args << QLatin1String("____TMP_DIR____ = tempdir"); if (OctaveSettings::integratePlots()) { m_watch = new KDirWatch(this); m_watch->setObjectName(QLatin1String("OctaveDirWatch")); connect (m_watch, SIGNAL(dirty(QString)), SLOT(plotFileChanged(QString)) ); } // connect the signal and slots prior to staring octave to make sure we handle the very first output // in parserOutput() to determine the temp folder and the format of the promt connect ( m_process, SIGNAL ( readyReadStandardOutput() ), SLOT ( readOutput() ) ); connect ( m_process, SIGNAL ( readyReadStandardError() ), SLOT ( readError() ) ); connect ( m_process, SIGNAL ( error ( QProcess::ProcessError ) ), SLOT ( processError() ) ); m_process->setProgram ( OctaveSettings::path().toLocalFile(), args ); qDebug() << "starting " << m_process->program(); m_process->setOutputChannelMode ( KProcess::SeparateChannels ); m_process->start(); m_process->waitForStarted(); m_process->waitForReadyRead(); if(!OctaveSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = OctaveSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, OctaveExpression::DeleteOnFinish); } emit loginDone(); qDebug()<<"login done"; } void OctaveSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, nullptr, this, nullptr); // if(status()==Cantor::Session::Running) //TODO: terminate the running expressions first m_process->write("exit\n"); qDebug()<<"waiting for octave to finish"; m_process->waitForFinished(); qDebug()<<"octave exit finished"; if(m_process->state() != QProcess::NotRunning) { m_process->kill(); qDebug()<<"octave still running, process kill enforced"; } expressionQueue().clear(); delete m_process; m_process = nullptr; m_prompt = QRegExp(); m_tempDir.clear(); qDebug()<<"logout done"; } void OctaveSession::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 } expressionQueue().first()->interrupt(); expressionQueue().removeFirst(); foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Done); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } void OctaveSession::processError() { qDebug() << "processError"; emit error(m_process->errorString()); } -Cantor::Expression* OctaveSession::evaluateExpression ( const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior ) +Cantor::Expression* OctaveSession::evaluateExpression ( const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior, bool internal ) { qDebug() << "evaluating: " << command; - OctaveExpression* expression = new OctaveExpression ( this ); + OctaveExpression* expression = new OctaveExpression ( this, internal); expression->setCommand ( command ); expression->setFinishingBehavior ( finishingBehavior ); expression->evaluate(); return expression; } void OctaveSession::runFirstExpression() { OctaveExpression* expression = static_cast(expressionQueue().first()); connect(expression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); QString command = expression->command(); command.replace(QLatin1Char('\n'), QLatin1Char(',')); command += QLatin1Char('\n'); expression->setStatus(Cantor::Expression::Computing); m_process->write ( command.toLocal8Bit() ); } void OctaveSession::readError() { qDebug() << "readError"; QString error = QString::fromLocal8Bit(m_process->readAllStandardError()); if (!expressionQueue().isEmpty() && !error.isEmpty()) { static_cast(expressionQueue().first())->parseError(error); } } void OctaveSession::readOutput() { qDebug() << "readOutput"; while (m_process->bytesAvailable() > 0) { if (m_tempDir.isEmpty() && !m_process->canReadLine()) { qDebug() << "Waiting"; // Wait for the full line containing octave's tempDir return; } QString line = QString::fromLocal8Bit(m_process->readLine()); qDebug()<<"start parsing " << " " << line; if (expressionQueue().isEmpty() || m_prompt.isEmpty()) { // no expression is available, we're parsing the first output of octave after the start // -> determine the location of the temporary folder and the format of octave's promt if (m_prompt.isEmpty() && line.contains(QLatin1String(":1>"))) { qDebug() << "Found Octave prompt:" << line; line.replace(QLatin1String(":1"), QLatin1String(":[0-9]+")); m_prompt.setPattern(line); } else if (line.contains(QLatin1String("____TMP_DIR____"))) { m_tempDir = line; m_tempDir.remove(0,18); m_tempDir.chop(1); // isolate the tempDir's location qDebug() << "Got temporary file dir:" << m_tempDir; if (m_watch) { m_watch->addDir(m_tempDir, KDirWatch::WatchFiles); } } } else if (line.contains(m_prompt)) { // Check for errors before finalizing the expression // this makes sure that all errors are caught readError(); if (!expressionQueue().isEmpty()) { // Get command before finalize, because after finalizing the expression will be dequeued const QString& command = expressionQueue().first()->command(); static_cast(expressionQueue().first())->finalize(); if (command.contains(QLatin1String(" = "))) { emit variablesChanged(); } if (command.contains(QLatin1String("function "))) { emit functionsChanged(); } } } else { // Avoid many calls to setResult if a lot of output came at the same time while (m_process->canReadLine()) { line += QString::fromLocal8Bit(m_process->readLine()); } if (!expressionQueue().isEmpty()) static_cast(expressionQueue().first())->parseOutput(line); } } } void OctaveSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { qDebug() << "currentExpressionStatusChanged"; switch (status) { case Cantor::Expression::Done: case Cantor::Expression::Error: expressionQueue().removeFirst(); if (expressionQueue().isEmpty()) changeStatus(Done); else runFirstExpression(); break; default: break; } } void OctaveSession::plotFileChanged(const QString& filename) { if (!QFile::exists(filename) || !filename.split(QLatin1Char('/')).last().contains(QLatin1String("c-ob-"))) { return; } if (!expressionQueue().isEmpty()) { static_cast(expressionQueue().first())->parsePlotFile(filename); } } Cantor::CompletionObject* OctaveSession::completionFor ( const QString& cmd, int index ) { return new OctaveCompletionObject ( cmd, index, this ); } Cantor::SyntaxHelpObject* OctaveSession::syntaxHelpFor ( const QString& cmd ) { return new OctaveSyntaxHelpObject ( cmd, this ); } QSyntaxHighlighter* OctaveSession::syntaxHighlighter ( QObject* parent ) { OctaveHighlighter* highlighter = new OctaveHighlighter ( parent, this ); connect ( this, SIGNAL(functionsChanged()), highlighter, SLOT(updateFunctions()) ); connect ( this, SIGNAL(variablesChanged()), highlighter, SLOT(updateVariables()) ); return highlighter; } QAbstractItemModel* OctaveSession::variableModel() { return m_variableModel; } void OctaveSession::runSpecificCommands() { m_process->write("figure(1,'visible','off')"); } diff --git a/src/backends/octave/octavesession.h b/src/backends/octave/octavesession.h index 575670a2..5edd1bed 100644 --- a/src/backends/octave/octavesession.h +++ b/src/backends/octave/octavesession.h @@ -1,79 +1,79 @@ /* Copyright (C) 2010 Miha Čančula 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. */ #ifndef OCTAVESESSION_H #define OCTAVESESSION_H #include #include #include #include #include namespace Cantor { class DefaultVariableModel; } class KDirWatch; class OctaveExpression; class KProcess; class OctaveSession : public Cantor::Session { Q_OBJECT public: OctaveSession(Cantor::Backend* backend); ~OctaveSession() override = default; void interrupt() override; - Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior) override; + Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; void logout() override; void login() override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& cmd) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; QAbstractItemModel* variableModel() override; void runFirstExpression() override; private: KProcess* m_process; QTextStream m_stream; QRegExp m_prompt; KDirWatch* m_watch; QString m_tempDir; Cantor::DefaultVariableModel* m_variableModel; void readFromOctave(QByteArray data); private Q_SLOTS: void readOutput(); void readError(); void currentExpressionStatusChanged(Cantor::Expression::Status status); void processError(); void plotFileChanged(const QString& filename); void runSpecificCommands(); Q_SIGNALS: void functionsChanged(); void variablesChanged(); }; #endif // OCTAVESESSION_H diff --git a/src/backends/python/pythonexpression.cpp b/src/backends/python/pythonexpression.cpp index 1e019370..f838171b 100644 --- a/src/backends/python/pythonexpression.cpp +++ b/src/backends/python/pythonexpression.cpp @@ -1,112 +1,112 @@ /* 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 */ #include "pythonexpression.h" #include #include "textresult.h" #include "imageresult.h" #include "helpresult.h" #include #include #include #include "pythonsession.h" #include "settings.h" #include using PythonPlotResult = Cantor::ImageResult; -PythonExpression::PythonExpression(Cantor::Session* session) : Cantor::Expression(session) +PythonExpression::PythonExpression(Cantor::Session* session, bool internal) : Cantor::Expression(session, internal) { qDebug() << "PythonExpression construtor"; } void PythonExpression::evaluate() { setStatus(Cantor::Expression::Computing); PythonSession* pythonSession = dynamic_cast(session()); qDebug() << pythonSession->integratePlots() << command().contains(QLatin1String("show()")); if((pythonSession->integratePlots()) && (command().contains(QLatin1String("show()")))){ qDebug() << "Preparing export figures property"; QString saveFigCommand = QLatin1String("savefig('cantor-export-python-figure-%1.png')"); setCommand(command().replace(QLatin1String("show()"), saveFigCommand.arg(rand()))); qDebug() << "New Command " << command(); } pythonSession->runExpression(this); } void PythonExpression::parseOutput(QString output) { qDebug() << "output: " << output; if(command().simplified().startsWith(QLatin1String("help("))){ setResult(new Cantor::HelpResult(output.remove(output.lastIndexOf(QLatin1String("None")), 4))); } else { setResult(new Cantor::TextResult(output)); } setStatus(Cantor::Expression::Done); } void PythonExpression::parseError(QString error) { qDebug() << "error: " << error; setErrorMessage(error.replace(QLatin1String("\n"), QLatin1String("
"))); setStatus(Cantor::Expression::Error); } void PythonExpression::parsePlotFile(const QString& filename) { qDebug() << "parsePlotFile"; qDebug() << "PythonExpression::parsePlotFile: " << filename; setResult(new PythonPlotResult(QUrl::fromLocalFile(filename))); setPlotPending(false); if (m_finished) { qDebug() << "PythonExpression::parsePlotFile: done"; setStatus(Done); } } void PythonExpression::interrupt() { qDebug()<<"interruptinging command"; setStatus(Cantor::Expression::Interrupted); } void PythonExpression::setPlotPending(bool plot) { m_plotPending = plot; } diff --git a/src/backends/python/pythonexpression.h b/src/backends/python/pythonexpression.h index 69ae0b7f..84a9d268 100644 --- a/src/backends/python/pythonexpression.h +++ b/src/backends/python/pythonexpression.h @@ -1,48 +1,48 @@ /* 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 */ #ifndef _PYTHONEXPRESSION_H #define _PYTHONEXPRESSION_H #include "expression.h" #include class QTimer; class PythonExpression : public Cantor::Expression { Q_OBJECT public: - PythonExpression(Cantor::Session* session); + PythonExpression(Cantor::Session* session, bool internal); ~PythonExpression() override = default; void evaluate() override; void interrupt() override; void parseOutput(QString output); void parseError(QString error); void parsePlotFile(const QString& filename); void setPlotPending(bool plot); private: bool m_finished; bool m_plotPending; }; #endif /* _PYTHONEXPRESSION_H */ diff --git a/src/backends/python/pythonsession.cpp b/src/backends/python/pythonsession.cpp index 2a6bdb46..dbded637 100644 --- a/src/backends/python/pythonsession.cpp +++ b/src/backends/python/pythonsession.cpp @@ -1,462 +1,462 @@ /* 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 "pythonsession.h" #include "pythonexpression.h" #include "pythonhighlighter.h" #include "pythoncompletionobject.h" #include "pythonkeywords.h" #include "pythonutils.h" #include #include #include #include #include #include #include #include #include PythonSession::PythonSession(Cantor::Backend* backend, const QString serverName, const QString DbusChannelName) : Session(backend) , m_variableModel(new Cantor::DefaultVariableModel(this)) , m_pIface(nullptr) , m_pProcess(nullptr) , serverName(serverName) , DbusChannelName(DbusChannelName) { } 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); (*m_pProcess) << QStandardPaths::findExecutable(serverName); m_pProcess->start(); m_pProcess->waitForStarted(); m_pProcess->waitForReadyRead(); QTextStream stream(m_pProcess->readAllStandardOutput()); const QString& readyStatus = QString::fromLatin1("ready"); while (m_pProcess->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()); m_pIface = new QDBusInterface(serviceName, QString::fromLatin1("/"), QString(), QDBusConnection::sessionBus()); if (!m_pIface->isValid()) { qWarning() << QDBusConnection::sessionBus().lastError().message(); return; } m_pIface->call(QString::fromLatin1("login")); if(integratePlots()) { qDebug() << "integratePlots"; QString tempPath = QDir::tempPath(); QString pathOperations = tempPath; pathOperations.prepend(QLatin1String("import os\nos.chdir('")); pathOperations.append(QLatin1String("')\n")); qDebug() << "Processing command to change chdir in Python. Command " << pathOperations.toLocal8Bit(); getPythonCommandOutput(pathOperations); m_watch = new KDirWatch(this); m_watch->setObjectName(QLatin1String("PythonDirWatch")); m_watch->addDir(tempPath, KDirWatch::WatchFiles); qDebug() << "addDir " << tempPath << "? " << m_watch->contains(QLatin1String(tempPath.toLocal8Bit())); QObject::connect(m_watch, SIGNAL(created(QString)), SLOT(plotFileChanged(QString))); } const QStringList& scripts = autorunScripts(); if(!scripts.isEmpty()){ QString autorunScripts = scripts.join(QLatin1String("\n")); getPythonCommandOutput(autorunScripts); } const QString& importerFile = QLatin1String(":py/import_default_modules.py"); evaluateExpression(fromSource(importerFile), Cantor::Expression::DeleteOnFinish); listVariables(); emit loginDone(); } void PythonSession::logout() { // TODO: T6113, T6114 m_pProcess->terminate(); qDebug()<<"logout"; QDir removePlotFigures; QListIterator i(m_listPlotName); while(i.hasNext()){ removePlotFigures.remove(QLatin1String(i.next().toLocal8Bit().constData())); } } void PythonSession::interrupt() { // TODO: T6113, T6114 if (m_pProcess->pid()) m_pProcess->kill(); qDebug()<<"interrupt"; foreach(Cantor::Expression* e, m_runningExpressions) e->interrupt(); m_runningExpressions.clear(); changeStatus(Cantor::Session::Done); } -Cantor::Expression* PythonSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) +Cantor::Expression* PythonSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; - PythonExpression* expr = new PythonExpression(this); + PythonExpression* expr = new PythonExpression(this, internal); changeStatus(Cantor::Session::Running); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void PythonSession::runExpression(PythonExpression* expr) { qDebug() << "run expression"; m_currentExpression = expr; const QString& command = expr->command(); QStringList commandLine = command.split(QLatin1String("\n")); QString commandProcessing; for(const QString& command : commandLine){ const QString firstLineWord = command.trimmed().replace(QLatin1String("("), QLatin1String(" ")) .split(QLatin1String(" ")).at(0); // Ignore comments if (firstLineWord.length() != 0 && firstLineWord[0] == QLatin1Char('#')){ commandProcessing += command + QLatin1String("\n"); continue; } if(firstLineWord.contains(QLatin1String("execfile"))){ commandProcessing += command; continue; } if(!PythonKeywords::instance()->keywords().contains(firstLineWord) && !command.contains(QLatin1String("=")) && !command.endsWith(QLatin1String(":")) && !command.startsWith(QLatin1String(" "))){ commandProcessing += QLatin1String("print(") + command + QLatin1String(")\n"); continue; } commandProcessing += command + QLatin1String("\n"); } readExpressionOutput(commandProcessing); } // Is called asynchronously in the Python3 plugin void PythonSession::readExpressionOutput(const QString& commandProcessing) { readOutput(commandProcessing); } void PythonSession::runClassOutputPython() const { runPythonCommand(fromSource(QLatin1String(":py/init.py"))); } void PythonSession::getPythonCommandOutput(const QString& commandProcessing) { qDebug() << "Running python command" << commandProcessing; runClassOutputPython(); 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 { 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; } void PythonSession::expressionFinished() { qDebug()<< "finished"; PythonExpression* expression = qobject_cast(sender()); m_runningExpressions.removeAll(expression); qDebug() << "size: " << m_runningExpressions.size(); } void PythonSession::updateOutput() { if(m_error.isEmpty()){ m_currentExpression->parseOutput(m_output); qDebug() << "output: " << m_output; } else { m_currentExpression->parseError(m_error); qDebug() << "error: " << m_error; } listVariables(); changeStatus(Cantor::Session::Done); } void PythonSession::readOutput(const QString& commandProcessing) { qDebug() << "readOutput"; getPythonCommandOutput(commandProcessing); updateOutput(); } void PythonSession::plotFileChanged(const QString& filename) { qDebug() << "plotFileChanged filename:" << filename; if ((m_currentExpression) && (filename.contains(QLatin1String("cantor-export-python-figure")))) { qDebug() << "Calling parsePlotFile"; m_currentExpression->parsePlotFile(filename); m_listPlotName.append(filename); } } void PythonSession::listVariables() { QString listVariableCommand; listVariableCommand += QLatin1String("print(globals())\n"); getPythonCommandOutput(listVariableCommand); qDebug() << m_output; m_output.remove(QLatin1String("{")); m_output.remove(QLatin1String("<")); m_output.remove(QLatin1String(">")); m_output.remove(QLatin1String("}")); foreach(QString line, m_output.split(QLatin1String(", '"))){ QStringList parts = line.simplified().split(QLatin1String(":")); const QString& first = parts.first(); const QString& last = parts.last(); if(!first.startsWith(QLatin1String("'__")) && !first.startsWith(QLatin1String("__")) && !first.startsWith(QLatin1String("CatchOutPythonBackend'")) && !first.startsWith(QLatin1String("errorPythonBackend'")) && !first.startsWith(QLatin1String("outputPythonBackend'")) && !last.startsWith(QLatin1String(" class ")) && !last.startsWith(QLatin1String(" function ")) && !last.startsWith(QLatin1String(" module '") + first) /*skip imported modules*/ ) { m_variableModel->addVariable(parts.first().remove(QLatin1String("'")).simplified(), parts.last().simplified()); emit newVariable(parts.first().remove(QLatin1String("'")).simplified()); } } emit updateHighlighter(); } QSyntaxHighlighter* PythonSession::syntaxHighlighter(QObject* parent) { PythonHighlighter* highlighter = new PythonHighlighter(parent); QObject::connect(this, SIGNAL(updateHighlighter()), highlighter, SLOT(updateHighlight())); QObject::connect(this, SIGNAL(newVariable(const QString)), highlighter, SLOT(addVariable(const QString))); return highlighter; } Cantor::CompletionObject* PythonSession::completionFor(const QString& command, int index) { return new PythonCompletionObject(command, index, this); } QAbstractItemModel* PythonSession::variableModel() { 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(); } QString PythonSession::getError() const { // TODO: T6113, T6114 const QDBusReply& reply = m_pIface->call(QString::fromLatin1("getError")); if (reply.isValid()) return reply.value(); return reply.error().message(); } diff --git a/src/backends/python/pythonsession.h b/src/backends/python/pythonsession.h index fede9a4d..57784917 100644 --- a/src/backends/python/pythonsession.h +++ b/src/backends/python/pythonsession.h @@ -1,104 +1,104 @@ /* 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 namespace Cantor { class DefaultVariableModel; } class PythonExpression; class KDirWatch; class QDBusInterface; class KProcess; class CANTOR_EXPORT PythonSession : public Cantor::Session { Q_OBJECT public: PythonSession(Cantor::Backend* backend, const QString serverName, const QString DbusChannelName); ~PythonSession() override = default; void login() override; void logout() override; void interrupt() override; void runExpression(PythonExpression* expr); - Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave) 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; QAbstractItemModel* variableModel() override; virtual bool integratePlots() const = 0; virtual QStringList autorunScripts() const = 0; private: KDirWatch* m_watch; QStringList m_listPlotName; Cantor::DefaultVariableModel* m_variableModel; QList m_runningExpressions; PythonExpression* m_currentExpression; QDBusInterface* m_pIface; KProcess* m_pProcess; QString serverName; QString DbusChannelName; protected: QString m_output; QString m_error; private: void listVariables(); 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 runClassOutputPython() const; void updateOutput(); private Q_SLOTS: void readOutput(const QString& commandProcessing); void plotFileChanged(const QString& filename); void expressionFinished(); Q_SIGNALS: void updateHighlighter(); void newVariable(const QString variable); }; #endif /* _PYTHONSESSION_H */ diff --git a/src/backends/qalculate/qalculateexpression.cpp b/src/backends/qalculate/qalculateexpression.cpp index 66370a95..7aac8b1b 100644 --- a/src/backends/qalculate/qalculateexpression.cpp +++ b/src/backends/qalculate/qalculateexpression.cpp @@ -1,883 +1,883 @@ /************************************************************************************* * Copyright (C) 2009 by Milian Wolff * * Copyright (C) 2011 by Matteo Agostinelli * * * * 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 * *************************************************************************************/ #include #include "textresult.h" #include "helpresult.h" #include "imageresult.h" #include "epsresult.h" #include "settings.h" #include "defaultvariablemodel.h" #include "qalculateexpression.h" #include "qalculatesession.h" #include "qalculatesyntaxhelpobject.h" #include #include #include #include #include #include #include // required for the plotting interface of Qalculator #include #include #include #include #include #include #include #include #include -QalculateExpression::QalculateExpression( QalculateSession* session ) - : Cantor::Expression(session), m_tempFile(nullptr) +QalculateExpression::QalculateExpression( QalculateSession* session, bool internal) + : Cantor::Expression(session, internal), m_tempFile(nullptr) { } QalculateExpression::~QalculateExpression() { if (m_tempFile) delete m_tempFile; } void QalculateExpression::evaluate() { /* Use Api for: * help * plot Use qalc for any other command */ setStatus(Cantor::Expression::Computing); if (command().isEmpty()) { setStatus(Cantor::Expression::Done); return; } QStringList commands = command().split(QLatin1Char('\n')); foreach(const QString& command, commands) { if (command.contains(QLatin1String("help"))) { QalculateSyntaxHelpObject* helper = new QalculateSyntaxHelpObject(command, (QalculateSession*) session()); setResult(new Cantor::HelpResult(helper->answer())); setStatus(Cantor::Expression::Done); return; } else if (command.trimmed().startsWith(QLatin1String("plot")) && (command.indexOf(QLatin1String("plot"))+4 == command.size() || command[command.indexOf(QLatin1String("plot"))+4].isSpace())) { evaluatePlotCommand(); return; } } // we are here because the commands entered by user are regular commands. We would have returned by now otherwise QalculateSession* currentSession = dynamic_cast(session()); currentSession->runExpression(); } void QalculateExpression::parseOutput(QString& output) { output.remove(QLatin1String(">")); output = output.trimmed(); qDebug() << "output from qalc for command: " << command() << " " << output << endl; setResult(new Cantor::TextResult(output)); // update the variable model updateVariables(); setStatus(Cantor::Expression::Done); } void QalculateExpression::updateVariables() { QalculateSession* currentSession = dynamic_cast(session()); QMap &variables = currentSession->variables; Cantor::DefaultVariableModel* model = static_cast(currentSession->variableModel()); QMap::const_iterator it = variables.constBegin(); while (it != variables.constEnd()) { model->addVariable(it.key(), it.value()); ++it; } } void QalculateExpression::parseError(QString& error) { error.remove(QLatin1String(">")); error = error.trimmed(); qDebug() << "Error from qalc for command: " << command() << " " << error << endl; setErrorMessage(error); setStatus(Cantor::Expression::Error); } void QalculateExpression::interrupt() { setStatus(Cantor::Expression::Interrupted); } void QalculateExpression::evaluatePlotCommand() { QString argString = command().mid(command().indexOf(QLatin1String("plot"))+4); argString = QLatin1String(unlocalizeExpression(argString).c_str()); argString = argString.trimmed(); QList argumentsList; QStringList arguments; // For error handling KColorScheme scheme(QApplication::palette().currentColorGroup()); const QString errorColor = scheme.foreground(KColorScheme::NegativeText).color().name(); const QString warningColor = scheme.foreground(KColorScheme::NeutralText).color().name(); const QString msgFormat(QLatin1String("%2: %3
\n")); if (!CALCULATOR->canPlot()) { showMessage(i18n("Qalculate reports it cannot print. Is gnuplot installed?"), MESSAGE_ERROR); return; } // Split argString into the arguments int i=0; int j=0; QString arg = QLatin1String(""); while (i < argString.size()) { if (argString[i] == QLatin1Char('"') || argString[i] == QLatin1Char('\'')) { ++j; while(j < argString.size() && argString[j] != argString[i]) { if (argString[j] == QLatin1Char('\\')) { ++j; if (j == argString.size()) continue; // just ignore trailing backslashes } arg += argString[j]; ++j; } if (j == argString.size()) { showMessage(i18n("missing %1", argString[i]), MESSAGE_ERROR); return; } ++j; } else if (argString[i] == QLatin1Char(',')) { argumentsList.append(arguments); arguments.clear(); ++j; } else { while(j < argString.size() && !argString[j].isSpace() && argString[j] != QLatin1Char('=') && argString[j] != QLatin1Char(',')) { if (argString[j] == QLatin1Char('\\')) { ++j; if (j == argString.size()) continue; // just ignore trailing backslashes } arg += argString[j]; ++j; } } if (argString[j] == QLatin1Char('=')) { // Parse things like title="..." as one argument arg += QLatin1Char('='); i = ++j; continue; } if (!arg.isEmpty()) { arguments << arg; arg = QLatin1String(""); } while (j < argString.size() && argString[j].isSpace()) ++j; i = j; } argumentsList.append(arguments); // Parse the arguments and compute the points to be plotted std::vector y_vectors; std::vector x_vectors; std::vector plotDataParameterList; PlotParameters plotParameters; EvaluationOptions eo = evaluationOptions(); /// temporary plotParameters.title = ""; plotParameters.y_label = ""; plotParameters.x_label = ""; plotParameters.filename = ""; plotParameters.filetype = PLOT_FILETYPE_AUTO; plotParameters.color = QalculateSettings::coloredPlot(); plotParameters.auto_y_min = true; plotParameters.auto_x_min = true; plotParameters.auto_x_max = true; plotParameters.auto_y_max = true; plotParameters.y_log = false; plotParameters.x_log = false; plotParameters.grid = QalculateSettings::plotGrid(); plotParameters.linewidth = QalculateSettings::plotLineWidth(); plotParameters.show_all_borders = QalculateSettings::plotBorder(); switch (QalculateSettings::plotLegend()) { case QalculateSettings::LEGEND_NONE: plotParameters.legend_placement = PLOT_LEGEND_NONE; break; case QalculateSettings::LEGEND_TOP_LEFT: plotParameters.legend_placement = PLOT_LEGEND_TOP_LEFT; break; case QalculateSettings::LEGEND_TOP_RIGHT: plotParameters.legend_placement = PLOT_LEGEND_TOP_RIGHT; break; case QalculateSettings::LEGEND_BOTTOM_LEFT: plotParameters.legend_placement = PLOT_LEGEND_BOTTOM_LEFT; break; case QalculateSettings::LEGEND_BOTTOM_RIGHT: plotParameters.legend_placement = PLOT_LEGEND_BOTTOM_RIGHT; break; case QalculateSettings::LEGEND_BELOW: plotParameters.legend_placement = PLOT_LEGEND_BELOW; break; case QalculateSettings::LEGEND_OUTSIDE: plotParameters.legend_placement = PLOT_LEGEND_OUTSIDE; break; } bool plotInline = QalculateSettings::inlinePlot(); MathStructure xMin; MathStructure xMax; xMin.setUndefined(); xMax.setUndefined(); MathStructure stepLength; stepLength.setUndefined(); int steps = QalculateSettings::plotSteps(); QString mustBeNumber = i18n("%1 must be a number."); QString mustBeInteger = i18n("%1 must be a integer."); QString mustBeBoolean = i18n("%1 must be a boolean."); QString invalidOption = i18n("invalid option for %1: %2"); for (int i = 0; i < argumentsList.size(); ++i) { std::string xVariable = "x"; PlotDataParameters* plotDataParams = new PlotDataParameters; plotDataParameterList.push_back(plotDataParams); plotDataParams->title = ""; switch(QalculateSettings::plotSmoothing()) { case QalculateSettings::SMOOTHING_NONE: plotDataParams->smoothing = PLOT_SMOOTHING_NONE; break; case QalculateSettings::SMOOTHING_UNIQUE: plotDataParams->smoothing = PLOT_SMOOTHING_UNIQUE; break; case QalculateSettings::SMOOTHING_CSPLINES: plotDataParams->smoothing = PLOT_SMOOTHING_CSPLINES; break; case QalculateSettings::SMOOTHING_BEZIER: plotDataParams->smoothing = PLOT_SMOOTHING_BEZIER; break; case QalculateSettings::SMOOTHING_SBEZIER: plotDataParams->smoothing = PLOT_SMOOTHING_SBEZIER; break; } switch(QalculateSettings::plotStyle()) { case QalculateSettings::STYLE_LINES: plotDataParams->style = PLOT_STYLE_LINES; break; case QalculateSettings::STYLE_POINTS: plotDataParams->style = PLOT_STYLE_POINTS; break; case QalculateSettings::STYLE_LINES_POINTS: plotDataParams->style = PLOT_STYLE_POINTS_LINES; break; case QalculateSettings::STYLE_BOXES: plotDataParams->style = PLOT_STYLE_BOXES; break; case QalculateSettings::STYLE_HISTOGRAM: plotDataParams->style = PLOT_STYLE_HISTOGRAM; break; case QalculateSettings::STYLE_STEPS: plotDataParams->style = PLOT_STYLE_STEPS; break; case QalculateSettings::STYLE_CANDLESTICKS: plotDataParams->style = PLOT_STYLE_CANDLESTICKS; break; case QalculateSettings::STYLE_DOTS: plotDataParams->style = PLOT_STYLE_DOTS; break; } plotDataParams->yaxis2 = false; plotDataParams->xaxis2 = false; arguments = argumentsList[i]; std::string expression; int lastExpressionEntry = -1; for (int j = 0; j < arguments.size(); ++j) { QString argument = arguments[j]; // PlotParameters if (argument.startsWith(QLatin1String("plottitle="))) plotParameters.title = argument.mid(10).toLatin1().data(); else if (argument.startsWith(QLatin1String("ylabel="))) plotParameters.y_label = argument.mid(7).toLatin1().data(); else if (argument.startsWith(QLatin1String("xlabel="))) plotParameters.x_label = argument.mid(7).toLatin1().data(); else if (argument.startsWith(QLatin1String("filename="))) plotParameters.filename = argument.mid(9).toLatin1().data(); else if (argument.startsWith(QLatin1String("filetype="))) { QString option = argument.mid(9); if (option == QLatin1String("auto")) plotParameters.filetype = PLOT_FILETYPE_AUTO; else if (option == QLatin1String("png")) plotParameters.filetype = PLOT_FILETYPE_PNG; else if (option == QLatin1String("ps")) plotParameters.filetype = PLOT_FILETYPE_PS; else if (option == QLatin1String("eps")) plotParameters.filetype = PLOT_FILETYPE_EPS; else if (option == QLatin1String("latex")) plotParameters.filetype = PLOT_FILETYPE_LATEX; else if (option == QLatin1String("svg")) plotParameters.filetype = PLOT_FILETYPE_SVG; else if (option == QLatin1String("fig")) plotParameters.filetype = PLOT_FILETYPE_FIG; else { QString msg = invalidOption.arg(QLatin1String("filetype"), option); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("font="))) plotParameters.font = argument.mid(5).toLatin1().data(); else if (argument.startsWith(QLatin1String("color="))) { bool ok; plotParameters.color = stringToBool(argument.mid(6), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("color")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("ylog="))) { bool ok; plotParameters.y_log = stringToBool(argument.mid(5), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("ylog")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xlog="))) { bool ok; plotParameters.x_log = stringToBool(argument.mid(5), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("xlog")), MESSAGE_ERROR); return; } } else if (argument.startsWith(QLatin1String("ylogbase="))) { MathStructure ylogStr = CALCULATOR->calculate(argument.mid(9).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } if (ylogStr.isNumber()) { Number ylogNum = ylogStr.number(); plotParameters.y_log_base = ylogNum.floatValue(); } else { showMessage(mustBeNumber.arg(QLatin1String("ylogbase")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xlogbase="))) { MathStructure xlogStr = CALCULATOR->calculate(argument.mid(9).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } if (xlogStr.isNumber()) { Number xlogNum = xlogStr.number(); plotParameters.x_log_base = xlogNum.floatValue(); } else { showMessage(mustBeNumber.arg(QLatin1String("xlogbase")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("grid="))) { bool ok; plotParameters.grid = stringToBool(argument.mid(5), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("grid")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("linewidth="))) { MathStructure lineWidthStr = CALCULATOR->calculate(argument.mid(10).toLatin1().data(), eo); Number lineWidthNum; if (lineWidthStr.isNumber() && lineWidthStr.number().isInteger()) { lineWidthNum = lineWidthStr.number(); plotParameters.linewidth = lineWidthNum.intValue(); } else { showMessage(mustBeInteger.arg(QLatin1String("linewidth")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("border="))) { bool ok; plotParameters.show_all_borders = stringToBool(argument.mid(7), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("border")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("legend="))) { QString option = argument.mid(7); if (option == QLatin1String("none")) plotParameters.legend_placement = PLOT_LEGEND_NONE; else if (option == QLatin1String("top_left")) plotParameters.legend_placement = PLOT_LEGEND_TOP_LEFT; else if (option == QLatin1String("top_right")) plotParameters.legend_placement = PLOT_LEGEND_TOP_RIGHT; else if (option == QLatin1String("bottom_left")) plotParameters.legend_placement = PLOT_LEGEND_BOTTOM_LEFT; else if (option == QLatin1String("bottom_right")) plotParameters.legend_placement = PLOT_LEGEND_BOTTOM_RIGHT; else if (option == QLatin1String("below")) plotParameters.legend_placement = PLOT_LEGEND_BELOW; else if (option == QLatin1String("outside")) plotParameters.legend_placement = PLOT_LEGEND_OUTSIDE; else { QString msg = invalidOption.arg(QLatin1String("legend"), option); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } // PlotDataParameters else if (argument.startsWith(QLatin1String("title="))) { plotDataParams->title = argument.mid(6).toLatin1().data(); } else if (argument.startsWith(QLatin1String("smoothing="))) { QString option = argument.mid(10); if (option == QLatin1String("none")) plotDataParams->smoothing = PLOT_SMOOTHING_NONE; else if (option == QLatin1String("monotonic")) plotDataParams->smoothing = PLOT_SMOOTHING_UNIQUE; else if (option == QLatin1String("csplines")) plotDataParams->smoothing = PLOT_SMOOTHING_CSPLINES; else if (option == QLatin1String("bezier")) plotDataParams->smoothing = PLOT_SMOOTHING_BEZIER; else if (option == QLatin1String("sbezier")) plotDataParams->smoothing = PLOT_SMOOTHING_SBEZIER; else { QString msg = invalidOption.arg(QLatin1String("smoothing"), option); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("style="))) { QString option = argument.mid(6); if (option == QLatin1String("lines")) plotDataParams->style = PLOT_STYLE_LINES; else if (option == QLatin1String("points")) plotDataParams->style = PLOT_STYLE_POINTS; else if (option == QLatin1String("points_lines")) plotDataParams->style = PLOT_STYLE_POINTS_LINES; else if (option == QLatin1String("boxes")) plotDataParams->style = PLOT_STYLE_BOXES; else if (option == QLatin1String("histogram")) plotDataParams->style = PLOT_STYLE_HISTOGRAM; else if (option == QLatin1String("steps")) plotDataParams->style = PLOT_STYLE_STEPS; else if (option == QLatin1String("candlesticks")) plotDataParams->style = PLOT_STYLE_CANDLESTICKS; else if (option == QLatin1String("dots")) plotDataParams->style = PLOT_STYLE_DOTS; else { QString msg = invalidOption.arg(QLatin1String("style"), option); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xaxis2="))) { bool ok; plotDataParams->xaxis2 = stringToBool(argument.mid(7), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("xaxis2")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("yaxis2="))) { bool ok; plotDataParams->yaxis2 = stringToBool(argument.mid(7), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("yaxis2")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } // inline, xmin, xmax, step, steps, xvar // Custom options else if (argument.startsWith(QLatin1String("inline="))) { bool ok; plotInline = stringToBool(argument.mid(7), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("inline")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xmin="))) { xMin = CALCULATOR->calculate(argument.mid(5).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xmax="))) { xMax = CALCULATOR->calculate(argument.mid(5).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("step="))) { stepLength = CALCULATOR->calculate(argument.mid(5).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } steps = -1; } else if (argument.startsWith(QLatin1String("steps="))) { MathStructure stepsStr = CALCULATOR->calculate(argument.mid(6).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } Number stepsNum; if (stepsStr.isNumber() && stepsStr.number().isInteger()) { stepsNum = stepsStr.number(); steps = stepsNum.intValue(); stepLength.setUndefined(); } else { showMessage(mustBeInteger.arg(QLatin1String("steps")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xvar="))) { xVariable = argument.mid(5).toLatin1().data(); } else if (expression.empty()) { expression = argument.toLatin1().data(); lastExpressionEntry = j; } else if (lastExpressionEntry == j-1) { expression += " "; expression += argument.toLatin1().data(); lastExpressionEntry = j; } else { QString msg = i18n("found multiple expressions in one plot command (%1 and %2).", QLatin1String(expression.c_str()), argument); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } if (expression.empty()) continue; if (xMin.isUndefined()) { if (!plotParameters.auto_x_min) xMin = plotParameters.x_min; else xMin = 0.0; } if (xMax.isUndefined()) { if (!plotParameters.auto_x_max) xMax = plotParameters.x_max; else xMax = 10.0; } if (plotDataParams->title.empty()) plotDataParams->title = expression; MathStructure x_vec, y_vec; x_vec.clearVector(); if (!stepLength.isUndefined()) y_vec = CALCULATOR->expressionToPlotVector(expression, xMin, xMax, stepLength, &x_vec, xVariable, eo.parse_options); else y_vec = CALCULATOR->expressionToPlotVector(expression, xMin, xMax, steps, &x_vec, xVariable, eo.parse_options); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } x_vectors.push_back(x_vec); y_vectors.push_back(y_vec); //PrintOptions po; //y_vec.format(po); //setResult(new Cantor::TextResult(y_vec.print(po).c_str())); //setStatus(Done); //deletePlotDataParameters(plotDataParameterList); //return; } if (plotInline && plotParameters.filename.empty()) { // TODO: get a temporary file name here if (!m_tempFile) { #ifdef WITH_EPS m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_qalculate-XXXXXX.eps" )); #else m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_qalculate-XXXXXX.png")); #endif m_tempFile->open(); } plotParameters.filename = m_tempFile->fileName().toLatin1().data(); plotParameters.filetype = PLOT_FILETYPE_AUTO; } CALCULATOR->plotVectors(&plotParameters, y_vectors, x_vectors, plotDataParameterList); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } deletePlotDataParameters(plotDataParameterList); if (plotInline) { #ifdef WITH_EPS size_t p = plotParameters.filename.size(); if (plotParameters.filetype == PLOT_FILETYPE_EPS || plotParameters.filetype == PLOT_FILETYPE_PS || (plotParameters.filetype == PLOT_FILETYPE_AUTO && p >= 4 && plotParameters.filename.substr(p-4,4) == ".eps") || (plotParameters.filetype == PLOT_FILETYPE_AUTO && p >= 3 && plotParameters.filename.substr(p-3,3) == ".ps")) setResult(new Cantor::EpsResult(QUrl(QString::fromStdString(plotParameters.filename)))); else setResult(new Cantor::ImageResult(QUrl(QString::fromStdString(plotParameters.filename)))); #else setResult(new Cantor::ImageResult(QUrl::fromLocalFile(QString::fromStdString(plotParameters.filename)))); #endif setStatus(Cantor::Expression::Done); } } void QalculateExpression::showMessage(QString msg, MessageType mtype) { KColorScheme scheme(QApplication::palette().currentColorGroup()); const QString errorColor = scheme.foreground(KColorScheme::NegativeText).color().name(); const QString warningColor = scheme.foreground(KColorScheme::NeutralText).color().name(); const QString msgFormat(QLatin1String("%2: %3
\n")); if(mtype == MESSAGE_ERROR || mtype == MESSAGE_WARNING) { msg.replace(QLatin1String("&"), QLatin1String("&")); msg.replace(QLatin1String(">"), QLatin1String(">")); msg.replace(QLatin1String("<"), QLatin1String("<")); if (mtype == MESSAGE_ERROR) { msg = msgFormat.arg(errorColor, i18n("ERROR"), QLatin1String(msg.toLatin1().data())); } else { msg = msgFormat.arg(errorColor, i18n("WARNING"), QLatin1String(msg.toLatin1().data())); } setErrorMessage(msg); setStatus(Error); } else { KMessageBox::information(QApplication::activeWindow(), msg); } } EvaluationOptions QalculateExpression::evaluationOptions() { EvaluationOptions eo; eo.auto_post_conversion = QalculateSettings::postConversion() ? POST_CONVERSION_BEST : POST_CONVERSION_NONE; eo.keep_zero_units = false; eo.parse_options = parseOptions(); switch (QalculateSettings::structuring()) { case 0: eo.structuring = STRUCTURING_NONE; break; case 1: eo.structuring = STRUCTURING_SIMPLIFY; break; case 2: eo.structuring = STRUCTURING_FACTORIZE; break; } return eo; } ParseOptions QalculateExpression::parseOptions() { ParseOptions po; switch (QalculateSettings::angleUnit()) { case 0: po.angle_unit = ANGLE_UNIT_NONE; break; case 1: po.angle_unit = ANGLE_UNIT_RADIANS; break; case 2: po.angle_unit = ANGLE_UNIT_DEGREES; break; case 3: po.angle_unit = ANGLE_UNIT_GRADIANS; break; } po.base = QalculateSettings::base(); return po; } void QalculateExpression::deletePlotDataParameters (const std::vector& plotDataParameterList) { for(size_t i = 0; i < plotDataParameterList.size(); ++i) delete plotDataParameterList[i]; } bool QalculateExpression::stringToBool(const QString &string, bool *ok) { if (string == QLatin1String("true") || string == QLatin1String("1")) { *ok = true; return true; } else if (string == QLatin1String("false") || string == QLatin1String("0")) { *ok = true; return false; } else { *ok = false; return false; } } int QalculateExpression::checkForCalculatorMessages() { // error handling, most of it copied from qalculate-kde int msgType = MSG_NONE; if ( CALCULATOR->message() ) { QString msg; KColorScheme scheme(QApplication::palette().currentColorGroup()); const QString errorColor = scheme.foreground(KColorScheme::NegativeText).color().name(); const QString warningColor = scheme.foreground(KColorScheme::NeutralText).color().name(); const QString msgFormat(QLatin1String("%2: %3
\n")); MessageType mtype; while(true) { mtype = CALCULATOR->message()->type(); switch(mtype) { case MESSAGE_INFORMATION: msgType |= MSG_INFO; break; case MESSAGE_WARNING: msgType |= MSG_WARN; break; case MESSAGE_ERROR: msgType |= MSG_ERR; break; } if(mtype == MESSAGE_ERROR || mtype == MESSAGE_WARNING) { QString text = QLatin1String(CALCULATOR->message()->message().c_str()); text.replace(QLatin1String("&"), QLatin1String("&")); text.replace(QLatin1String(">"), QLatin1String(">")); text.replace(QLatin1String("<"), QLatin1String("<")); if (mtype == MESSAGE_ERROR) { msg.append(msgFormat.arg(errorColor, i18n("ERROR"), text)); } else { msg.append(msgFormat.arg(errorColor, i18n("WARNING"), text)); } } else { KMessageBox::information(QApplication::activeWindow(), QLatin1String(CALCULATOR->message()->message().c_str())); } if(!CALCULATOR->nextMessage()) break; } if ( !msg.isEmpty() ) { m_message += msg; setErrorMessage(m_message); setStatus(Error); } } return msgType; } std::string QalculateExpression::unlocalizeExpression(QString expr) { // copy'n'pasted from qalculate plasma applet return CALCULATOR->unlocalizeExpression( expr.replace(QChar(0xA3), QLatin1String("GBP")) .replace(QChar(0xA5), QLatin1String("JPY")) .replace(QLatin1String("$"), QLatin1String("USD")) .replace(QChar(0x20AC), QLatin1String("EUR")) .toLatin1().data() ); } QSharedPointer QalculateExpression::printOptions() { QSharedPointer po(new PrintOptions); switch (QalculateSettings::fractionFormat()) { case 0: po->number_fraction_format = FRACTION_DECIMAL; break; case 1: po->number_fraction_format = FRACTION_DECIMAL_EXACT; break; case 2: po->number_fraction_format = FRACTION_FRACTIONAL; break; case 3: po->number_fraction_format = FRACTION_COMBINED; break; } po->indicate_infinite_series = QalculateSettings::indicateInfiniteSeries(); po->use_all_prefixes = QalculateSettings::useAllPrefixes(); po->negative_exponents = QalculateSettings::negativeExponents(); po->lower_case_e = true; po->base = QalculateSettings::base(); po->decimalpoint_sign = QLocale().decimalPoint().toLatin1(); switch (QalculateSettings::minExp()) { case 0: po->min_exp = EXP_NONE; break; case 1: po->min_exp = EXP_PURE; break; case 2: po->min_exp = EXP_SCIENTIFIC; break; case 3: po->min_exp = EXP_PRECISION; break; case 4: po->min_exp = EXP_BASE_3; break; } return po; } diff --git a/src/backends/qalculate/qalculateexpression.h b/src/backends/qalculate/qalculateexpression.h index bb0addfc..0c010840 100644 --- a/src/backends/qalculate/qalculateexpression.h +++ b/src/backends/qalculate/qalculateexpression.h @@ -1,64 +1,64 @@ /************************************************************************************* * Copyright (C) 2009 by Milian Wolff * * * * 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 * *************************************************************************************/ #ifndef QALCULATE_EXPRESSION_H #define QALCULATE_EXPRESSION_H #include "expression.h" #include #include #include #include class QalculateSession; class QTemporaryFile; class QalculateExpression : public Cantor::Expression { Q_OBJECT private: QTemporaryFile *m_tempFile; QString m_message; enum MsgType { MSG_NONE=0, MSG_INFO=1, MSG_WARN=2, MSG_ERR=4 }; void evaluatePlotCommand(); bool stringToBool(const QString&, bool*); void deletePlotDataParameters(const std::vector&); void showMessage(QString msg, MessageType mtype); int checkForCalculatorMessages(); void updateVariables(); QSharedPointer printOptions(); EvaluationOptions evaluationOptions(); ParseOptions parseOptions(); std::string unlocalizeExpression(QString expr); public: - QalculateExpression( QalculateSession* session); + QalculateExpression( QalculateSession* session, bool internal = false); ~QalculateExpression() override; void evaluate() override; void interrupt() override; void parseOutput(QString& output); void parseError(QString& error); }; #endif diff --git a/src/backends/qalculate/qalculatesession.cpp b/src/backends/qalculate/qalculatesession.cpp index 0025ae75..f12aeb9e 100644 --- a/src/backends/qalculate/qalculatesession.cpp +++ b/src/backends/qalculate/qalculatesession.cpp @@ -1,427 +1,427 @@ /************************************************************************************ * Copyright (C) 2009 by Milian Wolff * * Copyright (C) 2011 by Matteo Agostinelli * * * * 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 * *************************************************************************************/ #include "settings.h" #include "qalculatesession.h" #include "qalculatecompletionobject.h" #include "qalculatehighlighter.h" #include "defaultvariablemodel.h" #include #include #include #include #include #include #include #include #include #include #include "qalculatesyntaxhelpobject.h" QalculateSession::QalculateSession( Cantor::Backend* backend) : Session(backend), m_variableModel(new Cantor::DefaultVariableModel(this)), m_process(nullptr), m_currentExpression(nullptr), m_isSaveCommand(false) { /* qalc does all of this by default but we still need the CALCULATOR instance for plotting graphs */ if ( !CALCULATOR ) { new Calculator(); CALCULATOR->loadGlobalDefinitions(); CALCULATOR->loadLocalDefinitions(); CALCULATOR->loadExchangeRates(); } } QalculateSession::~QalculateSession() { CALCULATOR->abort(); if(m_process) m_process->kill(); } void QalculateSession::login() { emit loginStarted(); /* we will , most probably, use autoscripts for setting the mode , evaulate options, print options etc */ // if(!QalculateSettings::autorunScripts().isEmpty()){ // QString autorunScripts = QalculateSettings::self()->autorunScripts().join(QLatin1String("\n")); // // evaluateExpression(autorunScripts, QalculateExpression::DeleteOnFinish); // } /* set up the process here. The program path , arguments(if any),channel modes , and connections should all be set up here. once the setup is complete, start the process and inform the worksheet that we are ready */ m_process = new QProcess(this); m_process->setProgram(QStandardPaths::findExecutable(QLatin1String("qalc"))); m_process->setProcessChannelMode(QProcess::SeparateChannels); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readError())); connect(m_process, SIGNAL(started()), this, SLOT(processStarted())); m_process->start(); emit loginDone(); } void QalculateSession::readOutput() { while(m_process->bytesAvailable()) { m_output.append(QString::fromLocal8Bit(m_process->readLine())); qDebug() << m_output << endl; } if(m_currentExpression && !m_output.isEmpty() && m_output.trimmed().endsWith(QLatin1String(">"))) { // check if the commandQueue is empty or not . if it's not empty run the "runCommandQueue" function. // store the output in finalOutput and clear m_output if(m_currentCommand.trimmed().isEmpty()) m_output.clear(); if(!m_output.toLower().contains(QLatin1String("error")) && m_isSaveCommand) { storeVariables(m_currentCommand, m_output); m_isSaveCommand = false; } m_output = m_output.trimmed(); m_output.remove(m_currentCommand); if (!m_output.isEmpty()) m_finalOutput.append(m_output); // we tried to perform a save operation but failed(see parseSaveCommand()).In such a case // m_output will be empty but m_saveError will contain the error message. if(!m_saveError.isEmpty()) { m_finalOutput.append(m_saveError); m_saveError.clear(); } m_finalOutput.append(QLatin1String("\n")); m_output.clear(); if (!m_commandQueue.isEmpty()) runCommandQueue(); else { qDebug () << "parsing output: " << m_finalOutput << endl; m_currentExpression->parseOutput(m_finalOutput); m_finalOutput.clear(); } } } void QalculateSession::storeVariables(QString& currentCmd, QString output) { // internally we pass save(value,variable) command to qlac to save the variables. see parseSaveCommand() // TODO: if the user if trying to override a default variable(constansts etc) or an existing variable, ask the user if he/she wants to override it or not. qDebug() << "save command " << currentCmd << endl; /** if we have reached here, we expect our variable model to be updated with new variables. In case the variable model is not updated, it most probably because we were not able to successfully parse the current command and output to extract variable and value This is probably not the best way to get the variable and value. But since qalc does not provide a way to get the list of variables, we will have to stick to parsing **/ QString value; QString var; QRegExp regex; // find the value regex.setPattern(QLatin1String("[\\s\\w\\W]+=\\s*([\\w\\W]+)")); if(regex.exactMatch(output)) { int pos = regex.indexIn(output); if (pos > -1) { value = regex.cap(1); value = value.trimmed(); value.replace(QLatin1String("\n"), QLatin1String("")); value.remove(QLatin1String(">")); } } //find the varaiable. // ex1: currentCmd = save(10, var_1,category, title): var_1 = variable // ex2: currentCmd = save(planet(jupter,mass), jupiter_mass, category, title): jupiter_mass = variable // Not the best regex. Cab be improved regex.setPattern(QLatin1String("\\s*save\\s*\\(\\s*[\\s\\w]+\\s*,([\\s\\w]+),*[\\w\\W]*\\)\\s*;*$|\\s*save\\s*\\(\\s*[\\s\\w\\W]+\\)\\s*,([\\s\\w]+),*[\\w\\W]*\\)\\s*;*$")); if(regex.exactMatch(currentCmd)) { int pos = regex.indexIn(currentCmd); if (pos > -1) { if(!regex.cap(1).trimmed().isEmpty()) var = regex.cap(1).trimmed(); else var = regex.cap(2).trimmed(); var = var.trimmed(); var.replace(QLatin1String("\n"), QLatin1String("")); var.remove(QLatin1String(">")); } } if(!value.isEmpty() && !var.isEmpty()) variables.insert(var, value); } void QalculateSession::readError() { QString error = QLatin1String(m_process->readAllStandardError()); if(m_currentExpression) { m_currentExpression->parseError(error); } } void QalculateSession::processStarted() { qDebug() << "process started " << m_process->program() << m_process->processId() << endl; } void QalculateSession::logout() { qDebug () << "logging out " << endl; if(m_process) { m_process->write("quit\n"); if(!m_process->waitForFinished(1000)) m_process->kill(); } } void QalculateSession::interrupt() { qDebug () << "interrupting .... " << endl; if(m_currentExpression) m_currentExpression->interrupt(); m_commandQueue.clear(); m_expressionQueue.clear(); m_output.clear(); m_finalOutput.clear(); m_currentCommand.clear(); m_currentExpression = nullptr; } void QalculateSession::runExpression() { const QString& command = m_currentExpression->command(); foreach(const QString& cmd, command.split(QLatin1Char('\n'))) { m_commandQueue.enqueue(cmd); } runCommandQueue(); } void QalculateSession::runCommandQueue() { if (!m_commandQueue.isEmpty()) { m_currentCommand = m_commandQueue.dequeue(); // parse the current command if it's a save/load/store command if( m_currentCommand.toLower().trimmed().startsWith(QLatin1String("save")) || m_currentCommand.toLower().trimmed().startsWith(QLatin1String("store")) || m_currentCommand.trimmed().startsWith(QLatin1String("saveVariables"))) { m_currentCommand = parseSaveCommand(m_currentCommand); } m_currentCommand = m_currentCommand.trimmed(); m_currentCommand += QLatin1String("\n"); m_process->write(m_currentCommand.toLocal8Bit()); } } QString QalculateSession::parseSaveCommand(QString& currentCmd) { /* make sure the command is: * fomatted correctly. e.g if the command is save(value,variable), we have to make sure that there is no space between save and '(', otherwise qalc waits for user input which is not supported by us as of now * supported save commands: save(value,variable,[category],[title]), save definitions, save mode, save var, store var, saveVariables filename */ QRegExp regex; regex.setCaseSensitivity(Qt::CaseInsensitive); regex.setPattern(QLatin1String("\\s*save\\s*definitions\\s*")); if(regex.exactMatch(currentCmd)) { // save the variables in ~/.cantor/backends/qalculate/definitions currentCmd.clear(); return currentCmd; } regex.setPattern(QLatin1String("\\s*save\\s*mode\\s*")); if(regex.exactMatch(currentCmd)) { // save the mode in ~/.cantor/backends/qalculate/cantor_qalc.cfg currentCmd.clear(); return currentCmd; } regex.setPattern(QLatin1String("\\s*saveVariables\\s*[\\w\\W]+")); if(regex.exactMatch(currentCmd)) { // save the variables in a file currentCmd.clear(); return currentCmd; } regex.setPattern(QLatin1String("\\s*store\\s*([a-zA-Z_]+[\\w]*)|\\s*save\\s*([a-zA-Z_]+[\\w]*)")); if(regex.exactMatch(currentCmd)) { m_isSaveCommand = true; int pos = regex.indexIn(currentCmd); if(pos > -1) { if(!regex.cap(1).trimmed().isEmpty()) currentCmd = QStringLiteral("save(%1, %2)").arg(QStringLiteral("ans")).arg(regex.cap(1).trimmed()); else currentCmd = QStringLiteral("save(%1, %2)").arg(QStringLiteral("ans")).arg(regex.cap(2).trimmed()); return currentCmd; } } regex.setPattern(QLatin1String("\\s*save\\s*(\\([\\w\\W]+\\))\\s*;*$")); if(regex.exactMatch(currentCmd)) { m_isSaveCommand = true; int pos = regex.indexIn(currentCmd); if (pos > -1) { currentCmd = QStringLiteral("save%1").arg(regex.cap(1).trimmed()); return currentCmd; } } /* If we have not returned by this point, it's because: * we did not parse the save command properly. This might be due to malformed regular expressions. * or the commnad given by the user is malformed. More likely to happen In both these cases we will simply return an empty string because we don't want qalc to run malformed queries, else it would wait for user input and hence Qprocess would never return a complete output and the expression will remain in 'calculating' state */ m_saveError = currentCmd + QLatin1String("\nError: Could not save.\n"); return QLatin1String(""); } void QalculateSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { // depending on the status of the expression change the status of the session; switch (status) { case Cantor::Expression::Computing: break; case Cantor::Expression::Interrupted: changeStatus(Cantor::Session::Done); break; case Cantor::Expression::Done: case Cantor::Expression::Error: qDebug() << " ****** STATUS " << status; changeStatus(Cantor::Session::Done); if(m_expressionQueue.size() > 0) m_expressionQueue.dequeue(); if(!m_expressionQueue.isEmpty()) runExpressionQueue(); } } -Cantor::Expression* QalculateSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) +Cantor::Expression* QalculateSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << " ** evaluating expression: " << cmd << endl; qDebug() << " size of expression queue: " << m_expressionQueue.size() << endl; changeStatus(Cantor::Session::Running); - QalculateExpression* expr = new QalculateExpression(this); + QalculateExpression* expr = new QalculateExpression(this, internal); expr->setFinishingBehavior(behave); expr->setCommand(cmd); m_expressionQueue.enqueue(expr); runExpressionQueue(); return expr; } void QalculateSession::runExpressionQueue() { if(!m_expressionQueue.isEmpty()) { if(!m_currentExpression) m_currentExpression = m_expressionQueue.head(); else { /* there was some expression that was being executed by cantor. We run the new expression only if the current expression's status is 'Done' or 'Error', if not , we simply return */ Cantor::Expression::Status expr_status = m_currentExpression->status(); if(expr_status != Cantor::Expression::Done && expr_status != Cantor::Expression::Error) return; } m_currentExpression = m_expressionQueue.head(); connect(m_currentExpression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); // start processing the expression m_currentExpression->evaluate(); } } Cantor::CompletionObject* QalculateSession::completionFor(const QString& command, int index) { return new QalculateCompletionObject(command, index, this); } Cantor::SyntaxHelpObject* QalculateSession::syntaxHelpFor(const QString& cmd) { return new QalculateSyntaxHelpObject(cmd, this); } QSyntaxHighlighter* QalculateSession::syntaxHighlighter(QObject* parent) { return new QalculateHighlighter(parent); } QAbstractItemModel* QalculateSession::variableModel() { return m_variableModel; } diff --git a/src/backends/qalculate/qalculatesession.h b/src/backends/qalculate/qalculatesession.h index 36c99ce6..3c815759 100644 --- a/src/backends/qalculate/qalculatesession.h +++ b/src/backends/qalculate/qalculatesession.h @@ -1,90 +1,90 @@ /************************************************************************************* * Copyright (C) 2009 by Milian Wolff * * * * 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 * *************************************************************************************/ #ifndef QALCULATE_SESSION_H #define QALCULATE_SESSION_H #include "session.h" #include "qalculateexpression.h" #include #include #include #include #include namespace Cantor { class DefaultVariableModel; } class QalculateEngine; class QProcess; class QalculateSession : public Cantor::Session { Q_OBJECT private: Cantor::DefaultVariableModel* m_variableModel; QProcess* m_process; QalculateExpression* m_currentExpression; QString m_output; QString m_finalOutput; QString m_currentCommand; QString m_saveError; QQueue m_expressionQueue; QQueue m_commandQueue; bool m_isSaveCommand; private: void runExpressionQueue(); void runCommandQueue(); QString parseSaveCommand(QString& currentCmd); void storeVariables(QString& currentCmd, QString output); public: QalculateSession( Cantor::Backend* backend); ~QalculateSession() override; void login() override; void logout() override; void interrupt() override; - Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave) override; + Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& cmd) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; void runExpression(); QAbstractItemModel* variableModel() override; public: QMap variables; public Q_SLOTS: void readOutput(); void readError(); void processStarted(); void currentExpressionStatusChanged(Cantor::Expression::Status status); }; #endif diff --git a/src/backends/sage/sageexpression.cpp b/src/backends/sage/sageexpression.cpp index aea26f14..1b4e422f 100644 --- a/src/backends/sage/sageexpression.cpp +++ b/src/backends/sage/sageexpression.cpp @@ -1,256 +1,256 @@ /* 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) 2009 Alexander Rieder */ #include "sageexpression.h" #include "sagesession.h" #include "textresult.h" #include "imageresult.h" #include "animationresult.h" #include "helpresult.h" #include #include #include #include -SageExpression::SageExpression( Cantor::Session* session ) : Cantor::Expression(session), +SageExpression::SageExpression( Cantor::Session* session, bool internal ) : Cantor::Expression(session, internal), m_isHelpRequest(false), m_promptCount(0) { } void SageExpression::evaluate() { m_imagePath.clear(); m_isHelpRequest=false; //check if this is a ?command if(command().startsWith(QLatin1Char('?'))||command().endsWith(QLatin1Char('?'))) m_isHelpRequest=true; //coun't how many newlines are in the command, //as sage will output one "sage: " or "....:" for //each. m_promptCount=command().count(QLatin1Char('\n'))+2; session()->enqueueExpression(this); } void SageExpression::interrupt() { qDebug()<<"interrupting"; dynamic_cast(session())->sendSignalToProcess(2); dynamic_cast(session())->waitForNextPrompt(); setStatus(Cantor::Expression::Interrupted); } void SageExpression::parseOutput(const QString& text) { QString output=text; //remove carriage returns, we only use \n internally output.remove(QLatin1Char('\r')); //replace appearing backspaces, as they mess the whole output up output.remove(QRegExp(QLatin1String(".\b"))); //replace Escape sequences (only tested with `ls` command) const QChar ESC(0x1b); output.remove(QRegExp(QString(ESC)+QLatin1String("\\][^\a]*\a"))); const QString promptRegexpBase(QLatin1String("(^|\\n)%1")); const QRegExp promptRegexp(promptRegexpBase.arg(QRegExp::escape(QLatin1String(SageSession::SagePrompt)))); const QRegExp altPromptRegexp(promptRegexpBase.arg(QRegExp::escape(QLatin1String(SageSession::SageAlternativePrompt)))); bool endsWithAlternativePrompt=output.endsWith(QLatin1String(SageSession::SageAlternativePrompt)); //remove all prompts. we do this in a loop, because after we removed the first prompt, //there could be a second one, that isn't matched by promptRegexp in the first run, because //it originally isn't at the beginning of a line. int index=-1, index2=-1; while ( (index=output.indexOf(promptRegexp)) != -1 || (index2=output.indexOf(altPromptRegexp)) != -1 ) { qDebug()<<"got prompt"<")); const bool isLatex=m_outputCache.contains(QLatin1String("\\newcommand{\\Bold}")); //Check if it's latex stuff if(isLatex) //It's latex stuff so encapsulate it into an eqnarray environment { stripped.prepend(QLatin1String("\\begin{eqnarray*}")); stripped.append(QLatin1String("\\end{eqnarray*}")); } //strip html tags if(isHtml) { stripped.remove( QRegExp( QLatin1String("<[a-zA-Z\\/][^>]*>") ) ); } if (stripped.endsWith(QLatin1Char('\n'))) stripped.chop(1); if (m_isHelpRequest) { //Escape whitespace stripped.replace( QLatin1Char(' '), QLatin1String(" ")); //make things quoted in `` `` bold stripped.replace(QRegExp(QLatin1String("``([^`]*)``")), QLatin1String("\\1")); result=new Cantor::HelpResult(stripped); } else { result=new Cantor::TextResult(stripped); } if(isLatex) result->setFormat(Cantor::TextResult::LatexFormat); setResult(result); } else { QMimeDatabase db; QMimeType type = db.mimeTypeForUrl(QUrl::fromLocalFile(m_imagePath)); if(type.inherits(QLatin1String("image/gif"))) setResult( new Cantor::AnimationResult(QUrl::fromLocalFile(m_imagePath ),i18n("Result of %1" , command() ) ) ); else setResult( new Cantor::ImageResult( QUrl::fromLocalFile(m_imagePath ),i18n("Result of %1" , command() ) ) ); } setStatus(Cantor::Expression::Done); } void SageExpression::onProcessError(const QString& msg) { QString errMsg=i18n("%1\nThe last output was: \n %2", msg, m_outputCache.trimmed()); setErrorMessage(errMsg); setStatus(Cantor::Expression::Error); } QString SageExpression::additionalLatexHeaders() { //The LaTeX sage needs the amsmath package and some specific macros. //So include them in the header. //More about the macros requirement in bug #312738 return QLatin1String("\\usepackage{amsmath}\n" \ "\\newcommand{\\ZZ}{\\Bold{Z}}\n" \ "\\newcommand{\\NN}{\\Bold{N}}\n" \ "\\newcommand{\\RR}{\\Bold{R}}\n" \ "\\newcommand{\\CC}{\\Bold{C}}\n" \ "\\newcommand{\\QQ}{\\Bold{Q}}\n" \ "\\newcommand{\\QQbar}{\\overline{\\QQ}}\n" \ "\\newcommand{\\GF}[1]{\\Bold{F}_{#1}}\n" \ "\\newcommand{\\Zp}[1]{\\ZZ_{#1}}\n" \ "\\newcommand{\\Qp}[1]{\\QQ_{#1}}\n" \ "\\newcommand{\\Zmod}[1]{\\ZZ/#1\\ZZ}\n" \ "\\newcommand{\\CDF}{\\Bold{C}}\n" \ "\\newcommand{\\CIF}{\\Bold{C}}\n" \ "\\newcommand{\\CLF}{\\Bold{C}}\n" \ "\\newcommand{\\RDF}{\\Bold{R}}\n" \ "\\newcommand{\\RIF}{\\Bold{I} \\Bold{R}}\n"\ "\\newcommand{\\RLF}{\\Bold{R}}\n" \ "\\newcommand{\\CFF}{\\Bold{CFF}}\n"); } diff --git a/src/backends/sage/sageexpression.h b/src/backends/sage/sageexpression.h index f3493d36..a6fdfae3 100644 --- a/src/backends/sage/sageexpression.h +++ b/src/backends/sage/sageexpression.h @@ -1,55 +1,55 @@ /* 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) 2009 Alexander Rieder */ #ifndef _SAGEEXPRESSION_H #define _SAGEEXPRESSION_H #include "expression.h" class SageExpression : public Cantor::Expression { Q_OBJECT public: - SageExpression(Cantor::Session*); + SageExpression(Cantor::Session*, bool internal = false); void evaluate() override; void interrupt() override; void parseOutput(const QString&); void parseError(const QString&); void addFileResult(const QString&); void onProcessError(const QString&); public Q_SLOTS: void evalFinished(); protected: QString additionalLatexHeaders() override; private: QString m_outputCache; QString m_imagePath; bool m_isHelpRequest; int m_promptCount; }; #endif /* _SAGEEXPRESSION_H */ diff --git a/src/backends/sage/sagesession.cpp b/src/backends/sage/sagesession.cpp index ec49b389..7a993bf6 100644 --- a/src/backends/sage/sagesession.cpp +++ b/src/backends/sage/sagesession.cpp @@ -1,500 +1,500 @@ /* 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) 2009 Alexander Rieder */ #include "sagesession.h" #include "sageexpression.h" #include "sagecompletionobject.h" #include "sagehighlighter.h" #include #include #include #include #include #include #include "settings.h" #ifndef Q_OS_WIN #include #endif const QByteArray SageSession::SagePrompt="sage: "; //Text, sage outputs after each command const QByteArray SageSession::SageAlternativePrompt="....: "; //Text, sage outputs when it expects further input //some commands that are run after login static QByteArray initCmd="os.environ['PAGER'] = 'cat' \n "\ "sage.misc.pager.EMBEDDED_MODE = True \n "\ "sage.misc.viewer.BROWSER='' \n "\ "sage.misc.viewer.viewer.png_viewer('false') \n" \ "sage.plot.plot3d.base.SHOW_DEFAULTS['viewer'] = 'tachyon' \n"\ "sage.misc.latex.EMBEDDED_MODE = True \n "\ "os.environ['PAGER'] = 'cat' \n "\ "%colors nocolor \n "\ "print '____TMP_DIR____', sage.misc.misc.SAGE_TMP\n"; static QByteArray newInitCmd= "__CANTOR_IPYTHON_SHELL__=get_ipython() \n "\ "__CANTOR_IPYTHON_SHELL__.autoindent=False\n "; static QByteArray legacyInitCmd= "__CANTOR_IPYTHON_SHELL__=__IPYTHON__ \n " \ "__CANTOR_IPYTHON_SHELL__.autoindent=False\n "; static QByteArray endOfInitMarker="print '____END_OF_INIT____'\n "; SageSession::VersionInfo::VersionInfo(int major, int minor) { m_major=major; m_minor=minor; } int SageSession::VersionInfo::majorVersion() const { return m_major; } int SageSession::VersionInfo::minorVersion() const { return m_minor; } bool SageSession::VersionInfo::operator==(VersionInfo other) const { return m_major==other.m_major&&m_minor==other.m_minor; } bool SageSession::VersionInfo::operator<(VersionInfo other) const { return (m_major!= -1 && other.m_major==-1) || ( ((m_major!=-1 && other.m_major!=-1) || (m_major==other.m_major && m_major==-1) ) && ( m_major(SageSession::VersionInfo other) const { return !( (*this <= other )); } bool SageSession::VersionInfo::operator>=(SageSession::VersionInfo other) const { return !( *this < other); } SageSession::SageSession(Cantor::Backend* backend) : Session(backend) { m_isInitialized=false; m_haveSentInitCmd=false; connect( &m_dirWatch, SIGNAL( created( const QString& ) ), this, SLOT( fileCreated( const QString& ) ) ); } void SageSession::login() { qDebug()<<"login"; emit loginStarted(); m_process=new KPtyProcess(this); updateSageVersion(); const QString& sageExecFile = SageSettings::self()->path().toLocalFile(); if (m_sageVersion >= SageSession::VersionInfo(8, 4)) m_process->setProgram(sageExecFile, QStringList() << QLatin1String("--simple-prompt")); else { const QString& sageStartScript = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/sagebackend/cantor-execsage")); m_process->setProgram(sageStartScript, QStringList(sageExecFile)); } m_process->setOutputChannelMode(KProcess::SeparateChannels); m_process->setPtyChannels(KPtyProcess::AllChannels); m_process->pty()->setEcho(false); connect(m_process->pty(), SIGNAL(readyRead()), this, SLOT(readStdOut())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); connect(m_process, SIGNAL(finished ( int, QProcess::ExitStatus )), this, SLOT(processFinished(int, QProcess::ExitStatus))); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); m_process->start(); m_process->waitForStarted(); m_process->pty()->write(initCmd); if(!SageSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = SageSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, SageExpression::DeleteOnFinish); } emit loginDone(); } void SageSession::logout() { qDebug()<<"logout"; disconnect(m_process, SIGNAL(finished ( int, QProcess::ExitStatus )), this, SLOT(processFinished(int, QProcess::ExitStatus))); m_process->pty()->write("exit\n"); m_process->kill(); //Run sage-cleaner to kill all the orphans KProcess::startDetached(SageSettings::self()->path().toLocalFile(),QStringList()<setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void SageSession::readStdOut() { m_outputCache.append(QString::fromUtf8(m_process->pty()->readAll())); qDebug()<<"out: "<= SageSession::VersionInfo(7,4)) { const QString message = i18n( "Sage version %1.%2 is unsupported. Please update your installation "\ "to the supported versions to make it work with Cantor.", m_sageVersion.majorVersion(), m_sageVersion.minorVersion()); KMessageBox::error(nullptr, message, i18n("Cantor")); interrupt(); logout(); } else if(m_sageVersion<=SageSession::VersionInfo(5, 7)) { qDebug()<<"using an old version of sage: "<pty()->write(legacyInitCmd); defineCustomFunctions(); m_process->pty()->write(endOfInitMarker); m_haveSentInitCmd=true; } } else { qDebug()<<"using the current set of commands"; if(!m_haveSentInitCmd) { m_process->pty()->write(newInitCmd); defineCustomFunctions(); m_process->pty()->write(endOfInitMarker); m_haveSentInitCmd=true; } } } } int indexOfEOI=m_outputCache.indexOf(QLatin1String("____END_OF_INIT____")); if(indexOfEOI!=-1&&m_outputCache.indexOf(QLatin1String(SagePrompt), indexOfEOI)!=-1) { qDebug()<<"initialized"; //out.remove("____END_OF_INIT____"); //out.remove(SagePrompt); m_isInitialized=true; m_waitingForPrompt=false; runFirstExpression(); changeStatus(Cantor::Session::Done); m_outputCache.clear(); } //If we are waiting for another prompt, drop every output //until a prompt is found if(m_isInitialized&&m_waitingForPrompt) { qDebug()<<"waiting for prompt"; if(m_outputCache.contains(QLatin1String(SagePrompt))) m_waitingForPrompt=false; m_outputCache.clear(); return; } if(m_isInitialized&&!expressionQueue().isEmpty()) { SageExpression* expr = static_cast(expressionQueue().first()); expr->parseOutput(m_outputCache); m_outputCache.clear(); } } void SageSession::readStdErr() { qDebug()<<"reading stdErr"; QString out=QLatin1String(m_process->readAllStandardError()); qDebug()<<"err: "<(expressionQueue().first()); expr->parseError(out); } } void SageSession::currentExpressionChangedStatus(Cantor::Expression::Status status) { switch (status) { case Cantor::Expression::Done: case Cantor::Expression::Error: expressionQueue().removeFirst(); if (expressionQueue().isEmpty()) changeStatus(Done); else runFirstExpression(); break; default: break; } } void SageSession::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); if(exitStatus==QProcess::CrashExit) { if(!expressionQueue().isEmpty()) { static_cast(expressionQueue().last()) ->onProcessError(i18n("The Sage process crashed while evaluating this expression")); }else { //We don't have an actual command. it crashed for some other reason, just show a plain error message box KMessageBox::error(nullptr, i18n("The Sage process crashed"), i18n("Cantor")); } }else { if(!expressionQueue().isEmpty()) { static_cast(expressionQueue().last()) ->onProcessError(i18n("The Sage process exited while evaluating this expression")); }else { //We don't have an actual command. it crashed for some other reason, just show a plain error message box KMessageBox::error(nullptr, i18n("The Sage process exited"), i18n("Cantor")); } } } void SageSession::reportProcessError(QProcess::ProcessError e) { if(e==QProcess::FailedToStart) { changeStatus(Cantor::Session::Done); emit error(i18n("Failed to start Sage")); } } void SageSession::runFirstExpression() { if(!expressionQueue().isEmpty()&&m_isInitialized) { SageExpression* expr = static_cast(expressionQueue().first()); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); QString command=expr->command(); if(command.endsWith(QLatin1Char('?')) && !command.endsWith(QLatin1String("??"))) command=QLatin1String("help(")+command.left(command.size()-1)+QLatin1Char(')'); if(command.startsWith(QLatin1Char('?'))) command=QLatin1String("help(")+command.mid(1)+QLatin1Char(')'); qDebug()<<"writing "<setStatus(Cantor::Expression::Computing); m_process->pty()->write(QString(command+QLatin1String("\n\n")).toUtf8()); } } void SageSession::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 } expressionQueue().first()->interrupt(); expressionQueue().removeFirst(); foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Done); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); m_outputCache.clear(); } void SageSession::sendSignalToProcess(int signal) { qDebug()<<"sending signal....."<bash->sage-ipython QString cmd=QString::fromLatin1("pkill -%1 -f -P `pgrep -P %2 bash` .*sage-ipython.*").arg(signal).arg(m_process->pid()); KProcess proc(this); proc.setShellCommand(cmd); proc.execute(); } void SageSession::sendInputToProcess(const QString& input) { m_process->pty()->write(input.toUtf8()); } void SageSession::waitForNextPrompt() { m_waitingForPrompt=true; } void SageSession::fileCreated( const QString& path ) { qDebug()<<"got a file "<(expressionQueue().first()); if ( expr ) expr->addFileResult( path ); } } void SageSession::setTypesettingEnabled(bool enable) { Cantor::Session::setTypesettingEnabled(enable); // We have problems with Sage latex output (generates invalid code sometimes), so disable sage // latex output until this not be solved. Users can enable sage latex by hands using %display // sage magic. //tell the sage server to enable/disable pretty_print //const QString cmd=QLatin1String("__cantor_enable_typesetting(%1)"); //evaluateExpression(cmd.arg(enable ? QLatin1String("true"):QLatin1String("false")), Cantor::Expression::DeleteOnFinish); } void SageSession::setWorksheetPath(const QString& path) { //save the path to the worksheet as variable "__file__" //this variable is usually set by the "os" package when running a script //but when it is run in an interpreter (like sage server) it is not set const QString cmd = QLatin1String("__file__ = '%1'"); evaluateExpression(cmd.arg(path), Cantor::Expression::DeleteOnFinish); } Cantor::CompletionObject* SageSession::completionFor(const QString& command, int index) { return new SageCompletionObject(command, index, this); } QSyntaxHighlighter* SageSession::syntaxHighlighter(QObject* parent) { return new SageHighlighter(parent); } SageSession::VersionInfo SageSession::sageVersion() { return m_sageVersion; } void SageSession::defineCustomFunctions() { //typesetting QString cmd=QLatin1String("def __cantor_enable_typesetting(enable):\n"); if(m_sageVersion VersionInfo(5, 7) && m_sageVersion< VersionInfo(5, 12)) { cmd+=QLatin1String("\t sage.misc.latex.pretty_print_default(enable)\n\n"); }else { cmd+=QLatin1String("\t if(enable==true):\n "\ "\t \t %display typeset \n"\ "\t else: \n" \ "\t \t %display simple \n\n"); } sendInputToProcess(cmd); } bool SageSession::updateSageVersion() { QProcess get_sage_version; get_sage_version.setProgram(SageSettings::self()->path().toLocalFile()); get_sage_version.setArguments(QStringList()< */ #ifndef _SAGESESSION_H #define _SAGESESSION_H #include "session.h" #include "expression.h" #include #include class SageExpression; class KPtyProcess; class SageSession : public Cantor::Session { Q_OBJECT public: static const QByteArray SagePrompt; static const QByteArray SageAlternativePrompt; //small helper class to deal with sage versions //Note: major version -1 is treated as most current class VersionInfo{ public: VersionInfo(int major = -1, int minor = -1); //bool operator <=(VersionInfo v2); bool operator <(VersionInfo other) const; bool operator <=(VersionInfo other) const; bool operator >(VersionInfo other) const; bool operator >=(VersionInfo other) const; bool operator ==( VersionInfo other) const; // These are not called major() and minor() because some libc's have // macros with those names. int majorVersion() const; int minorVersion() const; private: int m_major; int m_minor; }; SageSession( Cantor::Backend* backend); ~SageSession() override = default; void login() override; void logout() override; - Cantor::Expression* evaluateExpression(const QString& command,Cantor::Expression::FinishingBehavior behave) override; + Cantor::Expression* evaluateExpression(const QString& command,Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; void runFirstExpression() override; void interrupt() override; void sendSignalToProcess(int signal); void sendInputToProcess(const QString& input); void waitForNextPrompt(); void setTypesettingEnabled(bool enable) override; void setWorksheetPath(const QString& path) override; Cantor::CompletionObject* completionFor(const QString& command, int index=-1) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; VersionInfo sageVersion(); public Q_SLOTS: void readStdOut(); void readStdErr(); private Q_SLOTS: void currentExpressionChangedStatus(Cantor::Expression::Status status); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); void reportProcessError(QProcess::ProcessError error); void fileCreated(const QString& path); private: void defineCustomFunctions(); bool updateSageVersion(); private: KPtyProcess* m_process; int m_isInitialized; QString m_tmpPath; KDirWatch m_dirWatch; bool m_waitingForPrompt; QString m_outputCache; VersionInfo m_sageVersion; bool m_haveSentInitCmd; }; #endif /* _SAGESESSION_H */ diff --git a/src/backends/scilab/scilabexpression.cpp b/src/backends/scilab/scilabexpression.cpp index c525ce0c..fe99a445 100644 --- a/src/backends/scilab/scilabexpression.cpp +++ b/src/backends/scilab/scilabexpression.cpp @@ -1,151 +1,151 @@ /* 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) 2011 Filipe Saraiva */ #include "scilabexpression.h" #include #include "textresult.h" #include "imageresult.h" #include "helpresult.h" #include #include #include #include #include "settings.h" #include "defaultvariablemodel.h" using ScilabPlotResult = Cantor::ImageResult; -ScilabExpression::ScilabExpression( Cantor::Session* session ) : Cantor::Expression(session) +ScilabExpression::ScilabExpression( Cantor::Session* session, bool internal ) : Cantor::Expression(session, internal) { qDebug() << "ScilabExpression construtor"; } void ScilabExpression::evaluate() { if((ScilabSettings::integratePlots()) && (command().contains(QLatin1String("plot")))){ qDebug() << "Preparing export figures property"; QString exportCommand; QStringList commandList = command().split(QLatin1String("\n")); for(int count = 0; count < commandList.size(); count++){ if(commandList.at(count).toLocal8Bit().contains("plot")){ exportCommand = QString::fromLatin1("\nxs2png(gcf(), 'cantor-export-scilab-figure-%1.png');\ndelete(gcf());").arg(rand()); commandList[count].append(exportCommand); exportCommand.clear(); } qDebug() << "Command " << count << ": " << commandList.at(count).toLocal8Bit().constData(); } QString newCommand = commandList.join(QLatin1String("\n")); newCommand.prepend(QLatin1String("clf();\n")); newCommand.append(QLatin1String("\n")); this->setCommand(newCommand); qDebug() << "New Command " << command(); } session()->enqueueExpression(this); } void ScilabExpression::parseOutput(QString output) { qDebug() << "output: " << output; m_output = output; setResult(new Cantor::TextResult(output)); evalFinished(); setStatus(Cantor::Expression::Done); } void ScilabExpression::parseError(QString error) { qDebug() << "error" << error; setErrorMessage(error.replace(QLatin1String("\n"), QLatin1String("
")).remove(0, 2) .replace(QLatin1String(" "), QLatin1String(" "))); evalFinished(); setStatus(Cantor::Expression::Error); } void ScilabExpression::parsePlotFile(QString filename) { qDebug() << "parsePlotFile"; qDebug() << "ScilabExpression::parsePlotFile: " << filename; setResult(new ScilabPlotResult(QUrl::fromLocalFile(filename))); setPlotPending(false); if (m_finished){ qDebug() << "ScilabExpression::parsePlotFile: done"; setStatus(Done); } } void ScilabExpression::interrupt() { qDebug()<<"interruptinging command"; setStatus(Cantor::Expression::Interrupted); } void ScilabExpression::evalFinished() { qDebug()<<"evaluation finished"; foreach (const QString& line, m_output.simplified().split(QLatin1Char('\n'), QString::SkipEmptyParts)){ if (m_output.contains(QLatin1Char('='))){ qDebug() << line; QStringList parts = line.split(QLatin1Char('=')); if (parts.size() >= 2){ Cantor::DefaultVariableModel* model = dynamic_cast(session()->variableModel()); if (model){ model->addVariable(parts.first().trimmed(), parts.last().trimmed()); } } } } } void ScilabExpression::setPlotPending(bool plot) { m_plotPending = plot; } diff --git a/src/backends/scilab/scilabexpression.h b/src/backends/scilab/scilabexpression.h index cc74a465..90112e65 100644 --- a/src/backends/scilab/scilabexpression.h +++ b/src/backends/scilab/scilabexpression.h @@ -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) 2011 Filipe Saraiva */ #ifndef _SCILABEXPRESSION_H #define _SCILABEXPRESSION_H #include "expression.h" #include class ScilabExpression : public Cantor::Expression { Q_OBJECT public: - ScilabExpression(Cantor::Session* session); + ScilabExpression(Cantor::Session* session, bool internal = false); ~ScilabExpression() override = default; void evaluate() override; void interrupt() override; void parseOutput(QString output); void parseError(QString error); void parsePlotFile(QString filename); void setPlotPending(bool plot); public Q_SLOTS: void evalFinished(); private: QString m_output; bool m_finished; bool m_plotPending; }; #endif /* _SCILABEXPRESSION_H */ diff --git a/src/backends/scilab/scilabsession.cpp b/src/backends/scilab/scilabsession.cpp index 134d6ea4..7755e6d2 100644 --- a/src/backends/scilab/scilabsession.cpp +++ b/src/backends/scilab/scilabsession.cpp @@ -1,269 +1,269 @@ /* 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) 2011 Filipe Saraiva */ #include "scilabsession.h" #include "scilabexpression.h" #include "scilabhighlighter.h" #include "scilabcompletionobject.h" #include #include #include #include #include #include #include #include #include #include #include ScilabSession::ScilabSession( Cantor::Backend* backend) : Session(backend), m_variableModel(new Cantor::DefaultVariableModel(this)) { m_process = nullptr; } ScilabSession::~ScilabSession() { if (m_process) m_process->terminate(); } void ScilabSession::login() { qDebug()<<"login"; emit loginStarted(); QStringList args; args << QLatin1String("-nb"); m_process = new QProcess(this); m_process->setArguments(args); m_process->setProgram(ScilabSettings::self()->path().toLocalFile()); qDebug() << m_process->program(); m_process->setProcessChannelMode(QProcess::SeparateChannels); m_process->start(); m_process->waitForStarted(); m_process->waitForReadyRead(); if(ScilabSettings::integratePlots()){ qDebug() << "integratePlots"; QString tempPath = QDir::tempPath(); QString pathScilabOperations = tempPath; pathScilabOperations.prepend(QLatin1String("chdir('")); pathScilabOperations.append(QLatin1String("');\n")); qDebug() << "Processing command to change chdir in Scilab. Command " << pathScilabOperations.toLocal8Bit(); m_process->write(pathScilabOperations.toLocal8Bit()); m_watch = new KDirWatch(this); m_watch->setObjectName(QLatin1String("ScilabDirWatch")); m_watch->addDir(tempPath, KDirWatch::WatchFiles); qDebug() << "addDir " << tempPath << "? " << m_watch->contains(QLatin1String(tempPath.toLocal8Bit())); QObject::connect(m_watch, &KDirWatch::created, this, &ScilabSession::plotFileChanged); } if(!ScilabSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = ScilabSettings::self()->autorunScripts().join(QLatin1String("\n")); m_process->write(autorunScripts.toLocal8Bit()); } QObject::connect(m_process, &QProcess::readyReadStandardOutput, this, &ScilabSession::readOutput); QObject::connect(m_process, &QProcess::readyReadStandardError, this, &ScilabSession::readError); m_process->readAllStandardOutput().clear(); m_process->readAllStandardError().clear(); changeStatus(Cantor::Session::Done); emit loginDone(); } void ScilabSession::logout() { qDebug()<<"logout"; m_process->write("exit\n"); expressionQueue().clear(); QDir removePlotFigures; QListIterator i(m_listPlotName); while(i.hasNext()){ removePlotFigures.remove(QLatin1String(i.next().toLocal8Bit().constData())); } } void ScilabSession::interrupt() { qDebug()<<"interrupt"; if (status() == Cantor::Session::Running) expressionQueue().first()->interrupt(); changeStatus(Cantor::Session::Done); } -Cantor::Expression* ScilabSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) +Cantor::Expression* ScilabSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; - ScilabExpression* expr = new ScilabExpression(this); + ScilabExpression* expr = new ScilabExpression(this, internal); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void ScilabSession::runFirstExpression() { qDebug() <<"call runFirstExpression"; qDebug() << "m_process: " << m_process; qDebug() << "status: " << (status() == Cantor::Session::Running ? "Running" : "Done"); if (!m_process) return; qDebug()<<"running next expression"; if(!expressionQueue().isEmpty()) { ScilabExpression* expr = static_cast(expressionQueue().first()); QString command; command.prepend(QLatin1String("\nprintf('begin-cantor-scilab-command-processing')\n")); command += expr->command(); command += QLatin1String("\nprintf('terminated-cantor-scilab-command-processing')\n"); connect(expr, &ScilabExpression::statusChanged, this, &ScilabSession::currentExpressionStatusChanged); expr->setStatus(Cantor::Expression::Computing); qDebug() << "Writing command to process" << command; m_process->write(command.toLocal8Bit()); } } void ScilabSession::readError() { qDebug() << "readError"; QString error = QLatin1String(m_process->readAllStandardError()); qDebug() << "error: " << error; static_cast(expressionQueue().first())->parseError(error); } void ScilabSession::readOutput() { qDebug() << "readOutput"; while(m_process->bytesAvailable() > 0){ m_output.append(QString::fromLocal8Bit(m_process->readLine())); } qDebug() << "output.isNull? " << m_output.isNull(); qDebug() << "output: " << m_output; if(status() != Running || m_output.isNull()){ return; } if(m_output.contains(QLatin1String("begin-cantor-scilab-command-processing")) && m_output.contains(QLatin1String("terminated-cantor-scilab-command-processing"))){ m_output.remove(QLatin1String("begin-cantor-scilab-command-processing")); m_output.remove(QLatin1String("terminated-cantor-scilab-command-processing")); static_cast(expressionQueue().first())->parseOutput(m_output); m_output.clear(); } } void ScilabSession::plotFileChanged(const QString& filename) { qDebug() << "plotFileChanged filename:" << filename; if (expressionQueue().first() && (filename.contains(QLatin1String("cantor-export-scilab-figure")))){ qDebug() << "Calling parsePlotFile"; static_cast(expressionQueue().first())->parsePlotFile(filename); m_listPlotName.append(filename); } } void ScilabSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { qDebug() << "currentExpressionStatusChanged: " << status; switch (status){ case Cantor::Expression::Computing: case Cantor::Expression::Interrupted: case Cantor::Expression::Queued: break; case Cantor::Expression::Done: case Cantor::Expression::Error: expressionQueue().removeFirst(); if (expressionQueue().isEmpty()) changeStatus(Done); else runFirstExpression(); break; } } QSyntaxHighlighter* ScilabSession::syntaxHighlighter(QObject* parent) { ScilabHighlighter *highlighter = new ScilabHighlighter(parent, this); return highlighter; } Cantor::CompletionObject* ScilabSession::completionFor(const QString& command, int index) { return new ScilabCompletionObject(command, index, this); } QAbstractItemModel* ScilabSession::variableModel() { return m_variableModel; } diff --git a/src/backends/scilab/scilabsession.h b/src/backends/scilab/scilabsession.h index c026386d..551582e8 100644 --- a/src/backends/scilab/scilabsession.h +++ b/src/backends/scilab/scilabsession.h @@ -1,74 +1,74 @@ /* 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) 2011 Filipe Saraiva */ #ifndef _SCILABSESSION_H #define _SCILABSESSION_H #include "session.h" #include "scilabexpression.h" #include #include namespace Cantor { class DefaultVariableModel; } class ScilabExpression; class KDirWatch; class QProcess; class ScilabSession : public Cantor::Session { Q_OBJECT public: ScilabSession(Cantor::Backend* backend); ~ScilabSession() override; void login() override; void logout() override; void interrupt() override; void runExpression(ScilabExpression* expr); QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; - Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave) 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; void runFirstExpression() override; QAbstractItemModel* variableModel() override; public Q_SLOTS: void readOutput(); void readError(); void plotFileChanged(const QString& filename); private: QProcess* m_process; KDirWatch* m_watch; QStringList m_listPlotName; QString m_output; Cantor::DefaultVariableModel* m_variableModel; private Q_SLOTS: void currentExpressionStatusChanged(Cantor::Expression::Status status); }; #endif /* _SCILABSESSION_H */ diff --git a/src/lib/expression.cpp b/src/lib/expression.cpp index fc684b5e..e6262fc7 100644 --- a/src/lib/expression.cpp +++ b/src/lib/expression.cpp @@ -1,258 +1,257 @@ /* 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) 2009 Alexander Rieder */ #include "expression.h" #include "latexrenderer.h" using namespace Cantor; #include #include "session.h" #include "result.h" #include "textresult.h" #include "imageresult.h" #include "latexresult.h" #include "settings.h" #include #include #include #include #include class Cantor::ExpressionPrivate { public: ExpressionPrivate() : status(Expression::Done), session(nullptr), - finishingBehavior(Expression::DoNotDelete), isInternal(false) + finishingBehavior(Expression::DoNotDelete), internal(false) { } int id; QString command; QString error; QList information; QVector results; Expression::Status status; Session* session; Expression::FinishingBehavior finishingBehavior; - bool isInternal; + bool internal; }; static const QString tex=QLatin1String("\\documentclass[12pt,fleqn]{article} \n "\ "\\usepackage{latexsym,amsfonts,amssymb,ulem} \n "\ "\\usepackage[dvips]{graphicx} \n "\ "\\setlength\\textwidth{5in} \n "\ "\\setlength{\\parindent}{0pt} \n "\ "%1 \n "\ "\\pagestyle{empty} \n "\ "\\begin{document} \n "\ "%2 \n "\ "\\end{document}\n"); -Expression::Expression( Session* session ) : QObject( session ), +Expression::Expression( Session* session, bool internal ) : QObject( session ), d(new ExpressionPrivate) { d->session=session; - d->id=session->nextExpressionId(); + d->internal = internal; + if (!internal) + d->id=session->nextExpressionId(); + else + d->id = -1; } Expression::~Expression() { qDeleteAll(d->results); delete d; } void Expression::setCommand(const QString& command) { d->command=command; } QString Expression::command() { return d->command; } void Expression::setErrorMessage(const QString& error) { d->error=error; } QString Expression::errorMessage() { return d->error; } void Expression::setResult(Result* result) { qDeleteAll(d->results); d->results.clear(); addResult(result); } void Expression::addResult(Result* result) { if(result!=nullptr) { qDebug()<<"settting result to a type "<type()<<" result"; #ifdef WITH_EPS //If it's text, and latex typesetting is enabled, render it if ( session()->isTypesettingEnabled()&& result->type()==TextResult::Type && dynamic_cast(result)->format()==TextResult::LatexFormat && !result->toHtml().trimmed().isEmpty() && finishingBehavior()!=DeleteOnFinish && !isInternal() ) { renderResultAsLatex(result); return; } #endif } d->results << result; emit gotResult(); } Result* Expression::result() { if (!d->results.isEmpty()) return d->results.first(); else return nullptr; } const QVector& Expression::results() const { return d->results; } void Expression::clearResult() { qDeleteAll(d->results); d->results.clear(); } void Expression::setStatus(Expression::Status status) { d->status=status; emit statusChanged(status); if(status==Expression::Done&&d->finishingBehavior==Expression::DeleteOnFinish) deleteLater(); } Expression::Status Expression::status() { return d->status; } Session* Expression::session() { return d->session; } void Expression::renderResultAsLatex(Result* result) { LatexRenderer* renderer=new LatexRenderer(this); renderer->setLatexCode(result->data().toString().trimmed()); renderer->addHeader(additionalLatexHeaders()); connect(renderer, &LatexRenderer::done, [=] { latexRendered(renderer, result); }); connect(renderer, &LatexRenderer::error, [=] { latexRendered(renderer, result); }); renderer->render(); } void Expression::latexRendered(LatexRenderer* renderer, Result* result) { qDebug()<<"rendered a result to "<imagePath(); //replace the textresult with the rendered latex image result //ImageResult* latex=new ImageResult( d->latexFilename ); if(renderer->renderingSuccessful()) { if (result->type() == TextResult::Type) { TextResult* r=dynamic_cast(result); LatexResult* latex=new LatexResult(r->data().toString().trimmed(), QUrl::fromLocalFile(renderer->imagePath()), r->plain()); addResult( latex ); } else if (result->type() == LatexResult::Type) { LatexResult* previousLatexResult=dynamic_cast(result); LatexResult* latex=new LatexResult(previousLatexResult->data().toString().trimmed(), QUrl::fromLocalFile(renderer->imagePath()), previousLatexResult->plain()); addResult( latex ); } }else { //if rendering with latex was not successful, just use the plain text version //if available TextResult* r=dynamic_cast(result); addResult(new TextResult(r->plain())); qDebug()<<"error rendering latex: "<errorMessage(); } delete result; renderer->deleteLater(); } void Expression::addInformation(const QString& information) { d->information.append(information); } QString Expression::additionalLatexHeaders() { return QString(); } int Expression::id() { return d->id; } void Expression::setId(int id) { d->id=id; emit idChanged(); } void Expression::setFinishingBehavior(Expression::FinishingBehavior behavior) { d->finishingBehavior=behavior; } Expression::FinishingBehavior Expression::finishingBehavior() { return d->finishingBehavior; } -void Expression::setInternal(bool internal) -{ - d->isInternal=internal; -} - bool Expression::isInternal() { - return d->isInternal; + return d->internal; } diff --git a/src/lib/expression.h b/src/lib/expression.h index 2b0debd8..72b0eeb5 100644 --- a/src/lib/expression.h +++ b/src/lib/expression.h @@ -1,253 +1,250 @@ /* 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) 2009 Alexander Rieder */ #ifndef _EXPRESSION_H #define _EXPRESSION_H #include #include #include "cantor_export.h" class KZip; /** * Namespace collecting all Classes of the Cantor Libraries */ namespace Cantor { class Session; class Result; class LatexRenderer; class ExpressionPrivate; /** * An Expression object is used, to store the information needed when running a command of a Session * Evaluation of Expression is an asynchroneous process in most cases, so most of the members * of this class are not useful directly after its construction. Therefore there are signals * indicating, when the Expression goes through the different stages of the Running process. * An Expression is never constructed directly, but by using Session::evaluateExpression() * * @author Alexander Rieder */ class CANTOR_EXPORT Expression : public QObject { Q_OBJECT public: enum Status{ Computing, ///< The Expression is still being computed Done, ///< The Running of the Expression is finished successfully Error, ///< An Error occurred when running the Expression Interrupted, ///< The Expression was interrupted by the user while running - Queued + Queued ///< The Expression is in expression queue, waited for Computing }; /** * Enum indicating how this Expression behaves on finishing */ enum FinishingBehavior { - DoNotDelete, ///< This Expression will not be deleted. This is the normal behaviour - DeleteOnFinish /** < The Object will delete itself when finished. This is used for fire-and-forget commands. - All output/results will be dropped - */ + DoNotDelete, ///< This Expression will not be deleted. This is the normal behaviour + DeleteOnFinish /** < The Object will delete itself when finished. This is used for fire-and-forget commands. + * All output/results will be dropped + */ }; /** * Expression constructor. Should only be called from Session::evaulateExpression * @param session the session, this Expression belongs to + * @param internal \c true if this expression is internal expression */ - Expression( Session* session ); + Expression( Session* session, bool internal = false); /** * destructor */ ~Expression() override; /** * Evaluate the Expression. before this is called, you should set the Command first * This method can be implemented asynchroneous, thus the Evaluation doesn't need to happen in the method, * It can also only be scheduled for evaluating. * @see setCommand() */ virtual void evaluate() = 0; /** * Interrupt the running of the Expression. * This should set the state to Interrupted. */ virtual void interrupt() = 0; /** * Returns the unique id of the Expression + * or -1 for internal expressions * @return the unique id of the Expression */ int id(); /** * set the id of the Expression. It should be unique * @param id the new Id */ void setId(int id); /** * set the finishing behaviour * @param behavior the new Finishing Behaviour */ void setFinishingBehavior(FinishingBehavior behavior); /** * get the Expressions finishing behaviour * @return the current finishing behaviour */ FinishingBehavior finishingBehavior(); /** * Sets the command, represented by this Expression * @param cmd the command */ void setCommand( const QString& cmd ); /** * Returns the command, represented by this Expression * @return the command, represented by this Expression */ QString command(); /** * Adds some additional information/input to this expression. * this is needed, when the Expression has emitted the needsAdditionalInformation signal, * and the user has answered the question. This is used for e.g. if maxima asks whether * n+1 is zero or not when running the command "integrate(x^n,x)" * This method is part of the InteractiveMode feature */ virtual void addInformation(const QString& information); /** * Sets the error message * @param cmd the error message * @see errorMessage() */ void setErrorMessage( const QString& cmd); /** * returns the Error message, if an error occurred during * the evaluation of the expression. * @return the error message */ QString errorMessage(); /** * The result of this Expression. It can have different types, represented by various * subclasses of Result, like text, image, etc. * The result will be null, until the computation is completed. * When the result changes, the gotResult() signal is emitted. * The Result object is owned by the Expression, and will get deleted, as * soon as the Expression dies, or newer results appear. * @return the result of the Expression, 0 if it isn't yet set */ Result* result(); /*! * in case the expression has multiple outputs/results, those can be obtained with this functions. * Everything else said for \sa result() applies here too. * @return the vector with results, or an empty vector if nor results are available yet. */ const QVector& results() const; /** * Deletes the result of this expression. * */ void clearResult(); /** * Returns the status of this Expression * @return the status of this Expression */ Status status(); /** * Set the status * statusChanged will be emitted * @param status the new status */ void setStatus(Status status); /** * Returns the Session, this Expression belongs to */ Session* session(); /** * returns whether or not this expression is internal, or * comes from the user */ bool isInternal(); - /** - * mark this expression as an internal expression, - * so for example latex will not be run on it - */ - void setInternal(bool internal); - + Q_SIGNALS: /** * the Id of this Expression changed */ void idChanged(); /** * A Result of the Expression has arrived */ void gotResult(); /** * the status of the Expression has changed. * @param status the new status */ void statusChanged(Cantor::Expression::Status status); /** * the Expression needs more information for the evaluation * @see addInformation() * @param question question, the user needs to answer */ void needsAdditionalInformation(const QString& question); //These are protected, because only subclasses will handle results/status changes protected: /** * Set the result of the Expression. * this will cause gotResult() to be emitted * The old result will be deleted, and the Expression * takes over ownership of the result object, taking * care of deleting it. * @param result the new result */ void setResult(Result* result); void addResult(Result*); protected: //returns a string of latex commands, that is inserted into the header. //used for example if special packages are needed virtual QString additionalLatexHeaders(); private: void renderResultAsLatex(Result* result); void latexRendered(LatexRenderer* renderer, Result* result); private: ExpressionPrivate* d; }; } #endif /* _EXPRESSION_H */ diff --git a/src/lib/session.h b/src/lib/session.h index 95a8fa74..50ca4c3e 100644 --- a/src/lib/session.h +++ b/src/lib/session.h @@ -1,218 +1,219 @@ /* 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) 2009 Alexander Rieder */ #ifndef _SESSION_H #define _SESSION_H #include #include "cantor_export.h" #include "expression.h" class QTextEdit; class QSyntaxHighlighter; class QAbstractItemModel; /** * Namespace collecting all Classes of the Cantor Libraries */ namespace Cantor { class Backend; class SessionPrivate; class CompletionObject; class SyntaxHelpObject; /** * The Session object is the main class used to interact with a Backend. * It is used to evaluate Expressions, get completions, syntax highlighting, etc. * * @author Alexander Rieder */ class CANTOR_EXPORT Session : public QObject { Q_OBJECT public: enum Status { Running, ///< the session is busy, running some expression Done ///< the session has done all the jobs, and is now waiting for more }; /** * Create a new Session. This should not yet set up the complete session, * thats job of the login() function * @see login() */ Session( Backend* backend); /** * Destructor */ ~Session() override; /** * Login to the Session. In this function you should do anything needed to set up * the session, and make it ready for usage. The method should be implemented non-blocking. * Emit loginStarted() prior to connection to the actual backend in order to notify cantor_part about it. * If the loging in is completed, the loginDone() signal must be emitted */ virtual void login() = 0; /** * Log out of the Session. Destroy everything specific to a single session, e.g. * stop all the running processes etc. * NOTE: restaring the session consists of first logout() and then login() */ virtual void logout() = 0; /** * Passes the given command to the backend and returns a Pointer * to a new Expression object, which will emit the gotResult() * signal as soon as the computation is done. The result will * then be acessible by Expression::result() * @param command the command that should be run by the backend. * @param finishingBehavior the FinishingBehaviour that should be used for this command. @see Expression::FinishingBehaviour + * @param isInternal true, if is it internal command @see Expression::Expression(Session*, bool) * @return an Expression object, representing this command */ - virtual Expression* evaluateExpression(const QString& command, Expression::FinishingBehavior finishingBehavior = Expression::FinishingBehavior::DoNotDelete) = 0; + virtual Expression* evaluateExpression(const QString& command, Expression::FinishingBehavior finishingBehavior = Expression::FinishingBehavior::DoNotDelete, bool internal = false) = 0; /** * Append the expression to queue . * @see expressionQueue() const */ void enqueueExpression(Expression*); /** * Interrupts all the running calculations in this session */ virtual void interrupt() = 0; /** * Returns tab-completion, for this command/command-part. * The return type is a CompletionObject. The fetching * of the completions works asynchronously, you'll have to * listen to the done() Signal of the returned object * @param cmd The partial command that should be completed * @param index The index (cursor position) at which completion * was invoked. Defaults to -1, indicating the end of the string. * @return a Completion object, representing this completion * @see CompletionObject */ virtual CompletionObject* completionFor(const QString& cmd, int index = -1); /** * Returns Syntax help, for this command. * It returns a SyntaxHelpObject, that will fetch the * needed information asynchroneousely. You need to listen * to the done() Signal of the Object * @param cmd the command, syntax help is requested for * @return SyntaxHelpObject, representing the help request * @see SyntaxHelpObject */ virtual SyntaxHelpObject* syntaxHelpFor(const QString& cmd); /** * returns a syntax highlighter for this session * @param parent QObject the Highlighter's parent * @return QSyntaxHighlighter doing the highlighting for this Session */ virtual QSyntaxHighlighter* syntaxHighlighter(QObject* parent); /** * returns a Model to interact with the variables * @return QAbstractItemModel to interact with the variables */ virtual QAbstractItemModel* variableModel(); /** * Enables/disables Typesetting for this session. * For this setting to make effect, the Backend must support * LaTeX typesetting (as indicated by the capabilities() flag. * @param enable true to enable, false to disable typesetting */ virtual void setTypesettingEnabled(bool enable); /** * Updates the worksheet path in the session. * This can be useful to set the path of the currently opened * Cantor project file in the backend interpreter. * Default implementation does nothing. Derived classes have * to implement the proper logic if this feature is supported. * @param path the new absolute path to the worksheet. */ virtual void setWorksheetPath(const QString& path); /** * Returns the Backend, this Session is for * @return the Backend, this Session is for */ Backend* backend(); /** * Returns the status this Session has * @return the status this Session has */ Cantor::Session::Status status(); /** * Returns whether typesetting is enabled or not * @return whether typesetting is enabled or not */ bool isTypesettingEnabled(); /** * Returns the next available Expression id * It is basically a counter, incremented for * each new Expression * @return next Expression id */ int nextExpressionId(); protected: /** * Change the status of the Session. This will cause the * stausChanged signal to be emitted * @param newStatus the new status of the session */ void changeStatus(Cantor::Session::Status newStatus); /** * Session can process one single expression at one time. * Any other expressions submitted by the user are queued first until they get processed. * The expression queue implements the FIFO mechanism. * The queud expression have the status \c Expression::Queued. */ QList& expressionQueue() const; /** * Execute first expression in expression queue. * Also, this function changes the status from Queued to Computing. * @see expressionQueue() const */ virtual void runFirstExpression(); Q_SIGNALS: void statusChanged(Cantor::Session::Status newStatus); void loginStarted(); void loginDone(); void error(const QString& msg); private: SessionPrivate* d; }; } #endif /* _SESSION_H */