diff --git a/src/backends/octave/octavesession.cpp b/src/backends/octave/octavesession.cpp index 778e2fc7..1a76f999 100644 --- a/src/backends/octave/octavesession.cpp +++ b/src/backends/octave/octavesession.cpp @@ -1,397 +1,394 @@ /* 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 "octavevariablemodel.h" 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_needUpdate(false), m_variableModel(new OctaveVariableModel(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); // Print the temp dir, used for plot files args << QLatin1String("--eval"); args << QLatin1String("printf('%s\\n', ['____TMP_DIR____ = ' tempdir]);"); // Do not show extra text in help commands args << QLatin1String("--eval"); args << QLatin1String("suppress_verbose_help_message(1);"); if (OctaveSettings::integratePlots()) { // Do not show the popup when plotting, rather only print to a file args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"off\");"); args << QLatin1String("--eval"); args << QLatin1String("graphics_toolkit gnuplot;"); } else { args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"on\");"); } 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); m_needUpdate = 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->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(); - qDebug() << "--- promptNumber" << promptNumber; - qDebug() << "--- m_previousPromptNumber" << m_previousPromptNumber; if (!expressionQueue().isEmpty()) { const QString& command = expressionQueue().first()->command(); if (m_previousPromptNumber + 1 == promptNumber || isSpecialOctaveCommand(command)) { if (!expressionQueue().isEmpty()) 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: m_needUpdate |= !expressionQueue().first()->isInternal(); expressionQueue().removeFirst(); if (expressionQueue().isEmpty()) if (m_needUpdate) { - qDebug() << "--- update"; m_variableModel->update(); m_needUpdate = false; } else 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 ( m_variableModel, &Cantor::DefaultVariableModel::variablesAdded, highlighter, &OctaveHighlighter::addUserVariable); connect ( m_variableModel, &Cantor::DefaultVariableModel::variablesRemoved, highlighter, &OctaveHighlighter::removeUserVariable); 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")); } diff --git a/src/backends/octave/octavevariablemodel.cpp b/src/backends/octave/octavevariablemodel.cpp index 02308416..fcfa8819 100644 --- a/src/backends/octave/octavevariablemodel.cpp +++ b/src/backends/octave/octavevariablemodel.cpp @@ -1,94 +1,88 @@ /* 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) 2018 Nikita Sirgienko */ #include "octavevariablemodel.h" #include "octavesession.h" #include "textresult.h" #include using namespace Cantor; OctaveVariableModel::OctaveVariableModel(OctaveSession* session): DefaultVariableModel(session), m_expr(nullptr) { } void OctaveVariableModel::update() { static const QString& cmd = QLatin1String( "printf('__cantor_delimiter_line__\\n');" "__cantor_list__ = who();" "for __cantor_index__ = 1:length(__cantor_list__)" " __cantor_varname__ = char(__cantor_list__{__cantor_index__});" " printf([__cantor_varname__ '\\n']);" " eval(['disp(' __cantor_varname__ ')']);" " printf('__cantor_delimiter_line__\\n')" "endfor;" "clear __cantor_list__;" "clear __cantor_index__;" "clear __cantor_varname__;" ); if (m_expr) return; m_expr = session()->evaluateExpression(cmd, Expression::FinishingBehavior::DoNotDelete, true); connect(m_expr, &Expression::statusChanged, this, &OctaveVariableModel::parseNewVariables); } void OctaveVariableModel::parseNewVariables(Expression::Status status) { switch(status) { case Expression::Status::Done: { static const QLatin1String delimiter("__cantor_delimiter_line__"); // Result always must be, if we done, so don't check it QString text = static_cast(m_expr->result())->plain(); - - qDebug() << "---- text" << text; const QStringList& lines = text.split(delimiter, QString::SkipEmptyParts); - qDebug() << "---- lines" << lines; QList vars; for (QString line : lines) { line = line.trimmed(); const QString& name = line.section(QLatin1String("\n"), 0, 0); const QString& value = line.section(QLatin1String("\n"), 1); - qDebug() << "--- name" << name; - qDebug() << "--- value" << value; vars << Variable{name, value}; } - qDebug() << "--- vars size" << vars.size(); setVariables(vars); break; } default: return; } m_expr->deleteLater(); m_expr = nullptr; } diff --git a/src/lib/defaultvariablemodel.cpp b/src/lib/defaultvariablemodel.cpp index baf93794..1c8a5ac5 100644 --- a/src/lib/defaultvariablemodel.cpp +++ b/src/lib/defaultvariablemodel.cpp @@ -1,282 +1,282 @@ /* 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) 2010 Miha Čančula */ #include "defaultvariablemodel.h" #include #include #include "extension.h" #include "backend.h" namespace Cantor { class DefaultVariableModelPrivate { public: QList variables; Session* session; VariableManagementExtension* extension; }; DefaultVariableModel::DefaultVariableModel(Session* session): QAbstractTableModel(session), d_ptr(new DefaultVariableModelPrivate) { Q_D(DefaultVariableModel); d->session = session; if (session) { d->extension = dynamic_cast(session->backend()->extension(QLatin1String("VariableManagementExtension"))); } qDebug() << d->session << d->extension; } int DefaultVariableModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return ColumnCount; } int DefaultVariableModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } else { Q_D(const DefaultVariableModel); return d->variables.size(); } } QVariant DefaultVariableModel::headerData(int section, Qt::Orientation orientation, int role) const { if(role==Qt::DisplayRole && orientation==Qt::Horizontal) { switch(section) { case NameColumn: return i18nc("@title:column", "Name"); case ValueColumn: return i18nc("@title:column", "Value"); break; } } return QVariant(); } Qt::ItemFlags DefaultVariableModel::flags(const QModelIndex& index) const { return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; } QVariant DefaultVariableModel::data(const QModelIndex& index, int role) const { if (role != Qt::DisplayRole || !index.isValid()) { return QVariant(); } Q_D(const DefaultVariableModel); switch (index.column()) { case NameColumn: return QVariant(d->variables[index.row()].name); case ValueColumn: return QVariant(d->variables[index.row()].value); default: return QVariant(); } } bool DefaultVariableModel::setData(const QModelIndex& index, const QVariant& value, int role) { if(role!=Qt::EditRole || !value.isValid() || !index.isValid()) { return false; } Q_D(const DefaultVariableModel); if(index.column() == ValueColumn) { // Changing values QString name = data(index.sibling(index.row(), NameColumn)).toString(); d->session->evaluateExpression(d->extension->setValue(name, value.toString()), Expression::DeleteOnFinish); return true; } else if(index.column() == NameColumn) { // Renaming => copy it, then delete the old one QString oldName = data(index).toString(); QString variableValue = data(index.sibling(index.row(), ValueColumn)).toString(); d->session->evaluateExpression(d->extension->addVariable(value.toString(), variableValue), Expression::DeleteOnFinish); d->session->evaluateExpression(d->extension->removeVariable(oldName), Expression::DeleteOnFinish); return true; } return false; } void DefaultVariableModel::addVariable(const QString& name, const QString& value) { Variable v; v.name = name; v.value = value; addVariable(v); } void DefaultVariableModel::addVariable(const Cantor::DefaultVariableModel::Variable& variable) { Q_D(DefaultVariableModel); if ( d->variables.contains(variable) ) { // TODO: Why we remove variable here? Set value properly removeVariable(variable); } beginInsertRows(QModelIndex(), d->variables.size(), d->variables.size()); d->variables.append(variable); emit variablesAdded(QStringList(variable.name)); endInsertRows(); } void DefaultVariableModel::removeVariable(const QString& name) { Variable v; v.name = name; removeVariable(v); } void DefaultVariableModel::removeVariable(const Cantor::DefaultVariableModel::Variable& variable) { Q_D(DefaultVariableModel); int row = d->variables.indexOf(variable); if(row==-1) return; const QString& name = variable.name; beginRemoveRows(QModelIndex(), row, row); d->variables.removeAt(row); endRemoveRows(); emit variablesRemoved(QStringList(name)); } void DefaultVariableModel::clearVariables() { Q_D(DefaultVariableModel); beginResetModel(); QStringList names; for (const Variable var: d->variables) names.append(var.name); d->variables.clear(); endResetModel(); emit variablesRemoved(names); } void DefaultVariableModel::setVariables(const QList& newVars) { Q_D(DefaultVariableModel); QStringList addedVars; QStringList removedVars; // Handle deleted vars - for (const Variable var : variables()) + int i = 0; + while (i < d->variables.size()) { + Variable var = d->variables[i]; bool found = false; for (const Variable& newvar : newVars) if(var.name == newvar.name) { found=true; break; } if (!found) + { removedVars << var.name; + beginRemoveRows(QModelIndex(), i, i); + d->variables.removeAt(i); + endRemoveRows(); + } + else + i++; } // Handle added vars const int size = d->variables.size(); for (const Variable newvar : newVars) { bool found = false; for (int i = 0; i < size; i++) if(d->variables[i].name == newvar.name) { found=true; if (d->variables[i].value != newvar.value) { - qDebug() << "--- update value for variable" << newvar.name << "to" << newvar.value; QModelIndex index = createIndex(i, ValueColumn); QAbstractItemModel::setData(index, newvar.value); d->variables[i].value = newvar.value; emit dataChanged(index, index); } break; } if (!found) { addedVars << newvar.name; beginInsertRows(QModelIndex(), d->variables.size(), d->variables.size()); d->variables.append(newvar); endInsertRows(); } } - for (const QString& var: removedVars) - { - int row = d->variables.indexOf(Variable{var,QString()}); - beginRemoveRows(QModelIndex(), row, row); - d->variables.removeAt(row); - endRemoveRows(); - } - emit variablesAdded(addedVars); emit variablesRemoved(removedVars); } Session* DefaultVariableModel::session() const { Q_D(const DefaultVariableModel); return d->session; } QList DefaultVariableModel::variables() const { Q_D(const DefaultVariableModel); return d->variables; } QStringList DefaultVariableModel::variableNames() const { Q_D(const DefaultVariableModel); QStringList names; for (const Variable var: d->variables) names << var.name; return names; } bool operator==(const Cantor::DefaultVariableModel::Variable& one, const Cantor::DefaultVariableModel::Variable& other) { return one.name == other.name; } }