diff --git a/src/backends/octave/octaveexpression.cpp b/src/backends/octave/octaveexpression.cpp index aea518d2..0a85a035 100644 --- a/src/backends/octave/octaveexpression.cpp +++ b/src/backends/octave/octaveexpression.cpp @@ -1,188 +1,173 @@ /* 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();"; +static const QLatin1String printCommand("cantor_print();"); OctaveExpression::OctaveExpression(Cantor::Session* session, bool internal): Expression(session, internal), m_plotPending(false), - m_finished(false), - m_appendPlotCommand(false), - m_appendDot(false) + m_finished(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"; - if (m_appendPlotCommand) - removeAppendedPlotCommand(); - 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))) + + m_finished = false; + session()->enqueueExpression(this); +} + +QString OctaveExpression::internalCommand() +{ + QString cmd = command(); + + if (m_plotPending && !cmd.contains(QLatin1String("cantor_plot")) && !cmd.contains(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; + cmd += printCommand; } - m_finished = false; - session()->enqueueExpression(this); -} + cmd.replace(QLatin1String(";\n"), QLatin1String(";")); + cmd.replace(QLatin1Char('\n'), QLatin1Char(',')); + cmd += QLatin1Char('\n'); + + return cmd; +} void OctaveExpression::parseOutput(const QString& output) { qDebug() << "parseOutput: " << output; - if (m_appendPlotCommand) - removeAppendedPlotCommand(); - if (!output.trimmed().isEmpty()) { // TODO: what about help in comment? printf with '... help ...'? // This must be corrected. if (command().contains(QLatin1String("help"))) { setResult(new Cantor::HelpResult(output)); } else { setResult(new Cantor::TextResult(output)); } } // TODO: remove this, then there is method for notify both Highlighter and variable model about new variable foreach ( const QString& line, output.simplified().split(QLatin1Char('\n'), QString::SkipEmptyParts) ) { if ((output.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) { const QString varname = parts.first().trimmed(); if (varname != QLatin1String("__cantor_tmp__")) model->addVariable(varname, parts.last().trimmed()); } } } } m_finished = true; if (!m_plotPending) setStatus(Done); } void OctaveExpression::parseError(const QString& error) { setErrorMessage(error); setStatus(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::setPlotPending(bool plot) { m_plotPending = plot; } - -void OctaveExpression::removeAppendedPlotCommand() -{ - 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); -} diff --git a/src/backends/octave/octaveexpression.h b/src/backends/octave/octaveexpression.h index ed186096..761fdb36 100644 --- a/src/backends/octave/octaveexpression.h +++ b/src/backends/octave/octaveexpression.h @@ -1,64 +1,61 @@ /* 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: explicit OctaveExpression(Cantor::Session*, bool internal = false); void interrupt() override; void evaluate() override; + QString internalCommand() override; + void parseOutput(const QString&); void parseError(const QString&); void parsePlotFile(const QString&); void setPlotPending(bool); -private: - void removeAppendedPlotCommand(); - private: QString m_resultString; bool m_plotPending; bool m_finished; - 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 3943ac83..343cb759 100644 --- a/src/backends/octave/octavesession.cpp +++ b/src/backends/octave/octavesession.cpp @@ -1,394 +1,391 @@ /* 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 "octavehighlighter.h" #include "result.h" #include "textresult.h" #include "settings.h" #include "octave-backend-config.h" #include "octavehighlighter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include const QRegExp OctaveSession::PROMPT_UNCHANGEABLE_COMMAND = QRegExp(QLatin1String("(,|;)+")); OctaveSession::OctaveSession ( Cantor::Backend* backend ) : Session ( backend ), m_process(nullptr), m_prompt(QLatin1String("CANTOR_OCTAVE_BACKEND_PROMPT:([0-9]+)> ")), m_subprompt(QLatin1String("CANTOR_OCTAVE_BACKEND_SUBPROMPT:([0-9]+)> ")), m_previousPromptNumber(1), m_watch(nullptr), m_syntaxError(false), 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"); // Setting prompt and subprompt args << QLatin1String("--eval"); args << QLatin1String("PS1('CANTOR_OCTAVE_BACKEND_PROMPT:\\#> ');"); args << QLatin1String("--eval"); args << QLatin1String("PS2('CANTOR_OCTAVE_BACKEND_SUBPROMPT:\\#> ');"); // 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)) ); } m_process->setProgram ( OctaveSettings::path().toLocalFile(), args ); qDebug() << "starting " << m_process->program(); m_process->setOutputChannelMode ( KProcess::SeparateChannels ); m_process->start(); m_process->waitForStarted(); // Got tmp dir bool loginFinished = false; QString input; while (!loginFinished) { m_process->waitForReadyRead(); input += QString::fromLatin1(m_process->readAllStandardOutput()); qDebug() << "login input: " << input; if (input.contains(QLatin1String("____TMP_DIR____"))) { m_tempDir = input; 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); } loginFinished = true; } } 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::self()->autorunScripts().isEmpty()){ QString autorunScripts = OctaveSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, OctaveExpression::DeleteOnFinish, true); } changeStatus(Cantor::Session::Done); 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_tempDir.clear(); m_output.clear(); m_previousPromptNumber = 1; m_variableModel->clearVariables(); changeStatus(Status::Disable); 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(); // Cleanup inner state and call octave prompt printing // If we move this code for interruption to Session, we need add function for // cleaning before setting Done status m_output.clear(); m_process->write("\n"); qDebug()<<"done interrupting"; } 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, bool internal ) { qDebug() << "evaluating: " << command; 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(QLatin1String(";\n"), QLatin1String(";")); - command.replace(QLatin1Char('\n'), QLatin1Char(',')); - command += QLatin1Char('\n'); + QString command = expression->internalCommand(); expression->setStatus(Cantor::Expression::Computing); if (isDoNothingCommand(command)) expression->setStatus(Cantor::Expression::Done); else { m_process->write ( command.toLocal8Bit() ); } } void OctaveSession::readError() { qDebug() << "readError"; QString error = QString::fromLocal8Bit(m_process->readAllStandardError()); if (!expressionQueue().isEmpty() && !error.isEmpty()) { OctaveExpression* const exp = static_cast(expressionQueue().first()); if (m_syntaxError) { m_syntaxError = false; exp->parseError(i18n("Syntax Error")); } else exp->parseError(error); m_output.clear(); } } void OctaveSession::readOutput() { qDebug() << "readOutput"; while (m_process->bytesAvailable() > 0) { QString line = QString::fromLocal8Bit(m_process->readLine()); qDebug()<<"start parsing " << " " << line; if (line.contains(m_prompt)) { const int promptNumber = m_prompt.cap(1).toInt(); if (!expressionQueue().isEmpty()) { const QString& command = expressionQueue().first()->command(); if (m_previousPromptNumber + 1 == promptNumber || isSpecialOctaveCommand(command)) { if (!expressionQueue().isEmpty()) { if (command.contains(QLatin1String(" = ")) || command.contains(QLatin1String("clear"))) { emit variablesChanged(); } static_cast(expressionQueue().first())->parseOutput(m_output); } } else { // Error command don't increase octave prompt number (usually, but not always) readError(); } } m_previousPromptNumber = promptNumber; m_output.clear(); } else if (line.contains(m_subprompt) && m_subprompt.cap(1).toInt() == m_previousPromptNumber) { // User don't write finished octave statement (for example, write 'a = [1,2, ' only), so // octave print subprompt and waits input finish. m_syntaxError = true; qDebug() << "subprompt catch"; m_process->write(")]'\"\n"); // forse exit from subprompt m_output.clear(); } else m_output += 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(variablesChanged()), highlighter, SLOT(updateVariables()) ); connect ( this, SIGNAL(statusChanged(Cantor::Session::Status)), highlighter, SLOT(sessionStatusChanged(Cantor::Session::Status)) ); return highlighter; } QAbstractItemModel* OctaveSession::variableModel() { return m_variableModel; } void OctaveSession::runSpecificCommands() { m_process->write("figure(1,'visible','off')"); } bool OctaveSession::isDoNothingCommand(const QString& command) { return PROMPT_UNCHANGEABLE_COMMAND.exactMatch(command) || command.isEmpty(); } bool OctaveSession::isSpecialOctaveCommand(const QString& command) { return command.contains(QLatin1String("completion_matches")); }