diff --git a/src/backends/maxima/maximasession.cpp b/src/backends/maxima/maximasession.cpp index 07d72437..3bd3f7f3 100644 --- a/src/backends/maxima/maximasession.cpp +++ b/src/backends/maxima/maximasession.cpp @@ -1,373 +1,362 @@ /* 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 #include //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, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(restartMaxima())); + disconnect(m_process, 0, this, 0); - if(status()==Cantor::Session::Done) - { - write(QLatin1String("quit();\n")); - -#ifdef Q_OS_WIN - //Give maxima time to clean up - qDebug()<<"waiting for maxima to finish"; +// if(status()==Cantor::Session::Running) + //TODO: terminate the running expressions first - m_process->waitForFinished(); -#endif - } - else - { - m_expressionQueue.clear(); - } + write(QLatin1String("quit();\n")); + qDebug()<<"waiting for maxima to finish"; + m_process->waitForFinished(); + qDebug()<<"maxima exit finished"; - //if it is still running, kill just kill it - if(m_process->state()!=QProcess::NotRunning) + if(m_process->state() != QProcess::NotRunning) { m_process->kill(); + qDebug()<<"maxima still running, process kill enforced"; } - qDebug()<<"done logging out"; - + m_expressionQueue.clear(); delete m_process; - m_process=nullptr; + m_process = nullptr; - qDebug()<<"destroyed maxima"; - - m_expressionQueue.clear(); + qDebug()<<"login done"; } Cantor::Expression* MaximaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) { qDebug() << "evaluating: " << cmd; MaximaExpression* expr = new MaximaExpression(this); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void MaximaSession::appendExpressionToQueue(MaximaExpression* expr) { m_expressionQueue.append(expr); qDebug()<<"queue: "<readAllStandardError()); if(m_expressionQueue.size()>0) { MaximaExpression* expr=m_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(m_expressionQueue.isEmpty()) { //queue is empty, interrupt was called, nothing to do here qDebug()<parseOutput(m_cache)) qDebug()<<"parsing successful"; else qDebug() << "failed to parse"; m_cache.clear(); } void MaximaSession::killLabels() { Cantor::Expression* e=evaluateExpression(QLatin1String("kill(labels);"), Cantor::Expression::DeleteOnFinish); e->setInternal(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(!m_expressionQueue.isEmpty()) { MaximaExpression* expr=m_expressionQueue.first(); QString command=expr->internalCommand(); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); if(command.isEmpty()) { qDebug()<<"empty command"; expr->forceDone(); } else { m_cache.clear(); write(command + QLatin1Char('\n')); } } } void MaximaSession::interrupt() { if(!m_expressionQueue.isEmpty()) m_expressionQueue.first()->interrupt(); m_expressionQueue.clear(); changeStatus(Cantor::Session::Done); } void MaximaSession::interrupt(MaximaExpression* expr) { if(expr==m_expressionQueue.first()) { qDebug()<<"interrupting " << expr->command(); disconnect(expr, nullptr, this, nullptr); #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else //TODO: interrupt the process on windows #endif qDebug()<<"done interrupting"; }else { m_expressionQueue.removeAll(expr); } } void MaximaSession::sendInputToProcess(const QString& input) { qDebug()<<"WARNING: use this method only if you know what you're doing. Use evaluateExpression to run commands"; qDebug()<<"running "<setInternal(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()<<"sending expression to maxima process: " << exp << endl; m_process->write(exp.toUtf8()); } diff --git a/src/backends/octave/octavesession.cpp b/src/backends/octave/octavesession.cpp index cb181112..d6dfe253 100644 --- a/src/backends/octave/octavesession.cpp +++ b/src/backends/octave/octavesession.cpp @@ -1,325 +1,344 @@ /* 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 #include #include OctaveSession::OctaveSession ( Cantor::Backend* backend ) : Session ( backend ), m_process(nullptr), m_currentExpression(nullptr), m_watch(nullptr), m_variableModel(new Cantor::DefaultVariableModel(this)) { qDebug() << octaveScriptInstallDir; } OctaveSession::~OctaveSession() { } 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"); 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(); qDebug()<readAllStandardOutput(); connect ( m_process, SIGNAL ( readyReadStandardOutput() ), SLOT ( readOutput() ) ); connect ( m_process, SIGNAL ( readyReadStandardError() ), SLOT ( readError() ) ); connect ( m_process, SIGNAL ( error ( QProcess::ProcessError ) ), SLOT ( processError() ) ); if (OctaveSettings::integratePlots()) { m_watch = new KDirWatch(this); m_watch->setObjectName(QLatin1String("OctaveDirWatch")); connect (m_watch, SIGNAL(dirty(QString)), SLOT(plotFileChanged(QString)) ); } 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"; + qDebug()<<"logout"; + + if(!m_process) + return; + + disconnect(m_process, 0, this, 0); + +// if(status()==Cantor::Session::Running) + //TODO: terminate the running expressions first + m_process->write("exit\n"); - if (!m_process->waitForFinished(1000)) + 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"; } + + m_expressionQueue.clear(); + delete m_process; + m_process = nullptr; + + qDebug()<<"login done"; } void OctaveSession::interrupt() { qDebug() << "interrupt"; if (m_currentExpression) { m_currentExpression->interrupt(); } m_expressionQueue.clear(); qDebug() << "Sending SIGINT to Octave"; #ifndef Q_OS_WIN kill(m_process->pid(), SIGINT); #else //TODO: interrupt the process on windows #endif - changeStatus(Done); } void OctaveSession::processError() { qDebug() << "processError"; emit error(m_process->errorString()); } Cantor::Expression* OctaveSession::evaluateExpression ( const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior ) { qDebug() << "evaluating: " << command; OctaveExpression* expression = new OctaveExpression ( this ); expression->setCommand ( command ); expression->setFinishingBehavior ( finishingBehavior ); expression->evaluate(); return expression; } void OctaveSession::runExpression ( OctaveExpression* expression ) { qDebug() << "runExpression"; if ( status() != Done ) { m_expressionQueue.enqueue ( expression ); qDebug() << m_expressionQueue.size(); } else { m_currentExpression = expression; changeStatus(Running); connect(m_currentExpression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); QString command = expression->command(); command.replace(QLatin1Char('\n'), QLatin1Char(',')); command += QLatin1Char('\n'); m_process->write ( command.toLocal8Bit() ); } } void OctaveSession::readError() { qDebug() << "readError"; QString error = QString::fromLocal8Bit(m_process->readAllStandardError()); if (!m_currentExpression || error.isEmpty()) { return; } m_currentExpression->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 (!m_currentExpression) { if (m_prompt.isEmpty() && line.contains(QLatin1String(":1>"))) { qDebug() << "Found Octave prompt:" << line; line.replace(QLatin1String(":1"), QLatin1String(":[0-9]+")); m_prompt.setPattern(line); changeStatus(Done); if (!m_expressionQueue.isEmpty()) { runExpression(m_expressionQueue.dequeue()); } emit loginDone(); } 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(); m_currentExpression->finalize(); if (m_currentExpression->command().contains(QLatin1String(" = "))) { emit variablesChanged(); } if (m_currentExpression->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()); } m_currentExpression->parseOutput(line); } } } void OctaveSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { qDebug() << "currentExpressionStatusChanged"; if (!m_currentExpression) { return; } switch (status) { case Cantor::Expression::Computing: break; case Cantor::Expression::Interrupted: break; case Cantor::Expression::Done: case Cantor::Expression::Error: changeStatus(Done); if (!m_expressionQueue.isEmpty()) { runExpression(m_expressionQueue.dequeue()); } break; } } void OctaveSession::plotFileChanged(const QString& filename) { if (!QFile::exists(filename) || !filename.split(QLatin1Char('/')).last().contains(QLatin1String("c-ob-"))) { return; } if (m_currentExpression) { m_currentExpression->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')"); }