diff --git a/src/backends/octave/octaveexpression.h b/src/backends/octave/octaveexpression.h --- a/src/backends/octave/octaveexpression.h +++ b/src/backends/octave/octaveexpression.h @@ -47,14 +47,12 @@ 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; diff --git a/src/backends/octave/octaveexpression.cpp b/src/backends/octave/octaveexpression.cpp --- a/src/backends/octave/octaveexpression.cpp +++ b/src/backends/octave/octaveexpression.cpp @@ -33,7 +33,6 @@ 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) { @@ -98,36 +97,6 @@ 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) { @@ -142,21 +111,25 @@ setCommand(cmd); } - if (!m_error && !m_resultString.trimmed().isEmpty()) + + 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(m_resultString)); + setResult(new Cantor::HelpResult(output)); } else { - setResult(new Cantor::TextResult(m_resultString)); + setResult(new Cantor::TextResult(output)); } } - foreach ( const QString& line, m_resultString.simplified().split(QLatin1Char('\n'), QString::SkipEmptyParts) ) + // 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 ((m_resultString.contains(QLatin1Char('='))) && !(command().startsWith(QLatin1String("help("))) + if ((output.contains(QLatin1Char('='))) && !(command().startsWith(QLatin1String("help("))) && !(command().contains(QLatin1String("help "))) && !(command().contains(QLatin1String("type(")))) { qDebug() << line; @@ -172,13 +145,35 @@ } } } - qDebug() << m_plotPending << m_error; + 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)) { - setStatus(m_error ? Error : Done); + 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; diff --git a/src/backends/octave/octavesession.h b/src/backends/octave/octavesession.h --- a/src/backends/octave/octavesession.h +++ b/src/backends/octave/octavesession.h @@ -39,40 +39,51 @@ { Q_OBJECT public: - OctaveSession(Cantor::Backend* backend); - ~OctaveSession() override = default; - void interrupt() 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; + OctaveSession(Cantor::Backend* backend); + ~OctaveSession() override = default; + void interrupt() 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; + + Q_SIGNALS: + void variablesChanged(); + + private: + const static QRegExp PROMPT_UNCHANGEABLE_COMMAND; private: KProcess* m_process; QTextStream m_stream; QRegExp m_prompt; + QRegExp m_subprompt; + int m_previousPromptNumber; KDirWatch* m_watch; QString m_tempDir; + bool m_loginFinish; + + QString m_output; Cantor::DefaultVariableModel* m_variableModel; + private: void readFromOctave(QByteArray data); + bool isDoNothingCommand(const QString& command); + bool isSpecialOctaveCommand(const QString& command); 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 variablesChanged(); + void plotFileChanged(const QString& filename); + void runSpecificCommands(); }; #endif // OCTAVESESSION_H diff --git a/src/backends/octave/octavesession.cpp b/src/backends/octave/octavesession.cpp --- a/src/backends/octave/octavesession.cpp +++ b/src/backends/octave/octavesession.cpp @@ -41,9 +41,15 @@ #include +const QRegExp OctaveSession::PROMPT_UNCHANGEABLE_COMMAND = QRegExp(QLatin1String("(,|;)+")); + OctaveSession::OctaveSession ( Cantor::Backend* backend ) : Session ( backend ), m_process(nullptr), m_watch(nullptr), +m_prompt(QLatin1String("CANTOR_OCTAVE_BACKEND_PROMPT:([0-9]+)> ")), +m_subprompt(QLatin1String("CANTOR_OCTAVE_BACKEND_SUBPROMPT:([0-9]+)> ")), +m_loginFinish(false), +m_previousPromptNumber(1), m_variableModel(new Cantor::DefaultVariableModel(this)) { qDebug() << octaveScriptInstallDir; @@ -60,6 +66,12 @@ 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); @@ -126,8 +138,8 @@ disconnect(m_process, nullptr, this, nullptr); -// if(status()==Cantor::Session::Running) - //TODO: terminate the running expressions first + // if(status()==Cantor::Session::Running) + // TODO: terminate the running expressions first m_process->write("exit\n"); qDebug()<<"waiting for octave to finish"; @@ -144,8 +156,11 @@ delete m_process; m_process = nullptr; - m_prompt = QRegExp(); + m_tempDir.clear(); + m_output.clear(); + m_previousPromptNumber = 1; + m_loginFinish = false; changeStatus(Status::Disable); @@ -168,10 +183,17 @@ } 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"; } @@ -200,10 +222,16 @@ 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'); expression->setStatus(Cantor::Expression::Computing); - m_process->write ( command.toLocal8Bit() ); + if (isDoNothingCommand(command)) + expression->setStatus(Cantor::Expression::Done); + else + { + m_process->write ( command.toLocal8Bit() ); + } } void OctaveSession::readError() @@ -213,6 +241,8 @@ if (!expressionQueue().isEmpty() && !error.isEmpty()) { static_cast(expressionQueue().first())->parseError(error); + + m_output.clear(); } } @@ -229,17 +259,11 @@ } QString line = QString::fromLocal8Bit(m_process->readLine()); qDebug()<<"start parsing " << " " << line; - if (expressionQueue().isEmpty() || m_prompt.isEmpty()) + if (!m_loginFinish) { - // 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____"))) + // we're parsing the first output of octave after the start + // -> determine the location of the temporary folder + if (line.contains(QLatin1String("____TMP_DIR____"))) { m_tempDir = line; m_tempDir.remove(0,18); @@ -249,35 +273,48 @@ { m_watch->addDir(m_tempDir, KDirWatch::WatchFiles); } + m_loginFinish = true; } } 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(" = "))) + + const int promptNumber = m_prompt.cap(1).toInt(); + if (m_previousPromptNumber + 1 == promptNumber || isSpecialOctaveCommand(command)) + { + if (!expressionQueue().isEmpty()) + { + if (command.contains(QLatin1String(" = "))) + { + emit variablesChanged(); + } + static_cast(expressionQueue().first())->parseOutput(m_output); + } + m_previousPromptNumber = promptNumber; + + } + else { - emit variablesChanged(); + // Error command don't increase octave prompt number + readError(); } } + m_output.clear(); } - else + else if (line.contains(m_subprompt) && m_subprompt.cap(1).toInt() == m_previousPromptNumber) { - // 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); - + // User don't write finished octave statement (for example, write 'a = [1,2, ' only), so + // octave print subprompt and waits input finish. + // TODO: replace this hack with printing something, that close statement with error + qDebug() << "subprompt catch"; + m_process->write(")]'\"\n"); + m_output.clear(); } + else + m_output += line; } } @@ -305,6 +342,7 @@ { return; } + if (!expressionQueue().isEmpty()) { static_cast(expressionQueue().first())->parsePlotFile(filename); @@ -340,3 +378,13 @@ { 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")); +}