diff --git a/src/backends/R/CMakeLists.txt b/src/backends/R/CMakeLists.txt --- a/src/backends/R/CMakeLists.txt +++ b/src/backends/R/CMakeLists.txt @@ -12,6 +12,7 @@ rhighlighter.cpp rkeywords.cpp rsettingswidget.cpp + rvariablemodel.cpp ) kconfig_add_kcfg_files(RBackend_SRCS rserver/settings.kcfgc) @@ -26,4 +27,15 @@ set_target_properties( cantor_rbackend PROPERTIES INSTALL_RPATH_USE_LINK_PATH false) target_link_libraries( cantor_rbackend ${R_USED_LIBS} KF5::SyntaxHighlighting) +if(BUILD_TESTING) + add_executable( testr testr.cpp) + add_test(testr testr) + ecm_mark_as_test(testr) + target_link_libraries( testr + Qt5::Test + cantorlibs + cantortest + ) +endif(BUILD_TESTING) + install( FILES cantor_r.knsrc DESTINATION ${KDE_INSTALL_CONFDIR} ) diff --git a/src/backends/R/rbackend.cpp b/src/backends/R/rbackend.cpp --- a/src/backends/R/rbackend.cpp +++ b/src/backends/R/rbackend.cpp @@ -65,10 +65,15 @@ Cantor::Backend::Capabilities RBackend::capabilities() const { qDebug()<<"Requesting capabilities of RSession"; - return Cantor::Backend::InteractiveMode | - Cantor::Backend::SyntaxHighlighting | - Cantor::Backend::Completion | - Cantor::Backend::VariableManagement; + Cantor::Backend::Capabilities cap= + SyntaxHighlighting| + Completion | + InteractiveMode; + + if (RServerSettings::variableManagement()) + cap |= VariableManagement; + + return cap; } bool RBackend::requirementsFullfilled() const diff --git a/src/backends/R/rhighlighter.h b/src/backends/R/rhighlighter.h --- a/src/backends/R/rhighlighter.h +++ b/src/backends/R/rhighlighter.h @@ -35,16 +35,18 @@ void highlightBlock(const QString &text) override; public Q_SLOTS: - void refreshSyntaxRegExps(); - void updateHighlighting(); - - Q_SIGNALS: - void syntaxRegExps(QVector&,QVector&); + void addUserVariable(const QStringList& vars); + void removeUserVariable(const QStringList& vars); + void addUserFunction(const QStringList& funcs); + void removeUserFunction(const QStringList& funcs); private: inline void formatRule(const QRegExp &p, const QTextCharFormat &fmt, const QString& text,bool shift=false); inline void massFormat(const QVector& rules, const QTextCharFormat &fmt, const QString& text,bool shift=false); + void addUserDefinition(const QStringList& names, QVector& vector); + void removeUserDefinition(const QStringList& names, QVector& vector); + static const QStringList operators_list; static const QStringList specials_list; QVector operators; diff --git a/src/backends/R/rhighlighter.cpp b/src/backends/R/rhighlighter.cpp --- a/src/backends/R/rhighlighter.cpp +++ b/src/backends/R/rhighlighter.cpp @@ -42,11 +42,6 @@ specials.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); } -void RHighlighter::refreshSyntaxRegExps() -{ - emit syntaxRegExps(variables,functions); -} - // FIXME: due to lack of lookbehinds in QRegExp here we use a flag showing if we need to shift the boundary of formatting // to make up for the accidentally matched character void RHighlighter::formatRule(const QRegExp &p, const QTextCharFormat &fmt, const QString& text,bool shift) @@ -86,7 +81,44 @@ formatRule(QRegExp(QLatin1String("\"[^\"]+\"")),stringFormat(),text); // WARNING a bit redundant } -void RHighlighter::updateHighlighting() +void RHighlighter::addUserDefinition(const QStringList& names, QVector& vector) { + for (const QString s : names) + if (!s.contains(QRegExp(QLatin1String("[^A-Za-z0-9_.]")))) + vector.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); + emit rulesChanged(); } + +void RHighlighter::removeUserDefinition(const QStringList& names, QVector& vector) +{ + for (const QString var : names) + for (int i = 0; i < vector.size(); i++) + if (vector[i].pattern() == QLatin1String("\\b")+var+QLatin1String("\\b")) + { + vector.remove(i); + break; + } + + emit rulesChanged(); +} + +void RHighlighter::addUserVariable(const QStringList& vars) +{ + addUserDefinition(vars, variables); +} + +void RHighlighter::removeUserVariable(const QStringList& vars) +{ + removeUserDefinition(vars, variables); +} + +void RHighlighter::removeUserFunction(const QStringList& funcs) +{ + removeUserDefinition(funcs, functions); +} + +void RHighlighter::addUserFunction(const QStringList& funcs) +{ + addUserDefinition(funcs, functions); +} diff --git a/src/backends/R/rserver/rserver.cpp b/src/backends/R/rserver/rserver.cpp --- a/src/backends/R/rserver/rserver.cpp +++ b/src/backends/R/rserver/rserver.cpp @@ -111,9 +111,6 @@ } qDebug()<<"done initializing"; - - // FIXME: other way to search symbols, see listSymbols for details - listSymbols(); } //Code from the RInside library @@ -367,9 +364,6 @@ showFiles(neededFiles); setStatus(Idle); - - // FIXME: Calling this every evaluation is probably ugly - listSymbols(); } void RServer::completeCommand(const QString& cmd) @@ -420,7 +414,7 @@ void RServer::listSymbols() { -// setStatus(RServer::Busy); + setStatus(RServer::Busy); QStringList vars, values, funcs; int errorOccurred; // TODO: error checks @@ -435,7 +429,7 @@ if (Rf_isFunction(value)) funcs << name; - else + else if (RServerSettings::variableManagement()) { int convertStatus; SEXP valueAsString = PROTECT(R_tryEval(lang2(install("toString"),value),nullptr,&convertStatus)); @@ -445,6 +439,8 @@ values << QString::fromUtf8(translateCharUTF8(asChar(valueAsString))); } } + else + vars << name; } UNPROTECT(1); @@ -463,8 +459,7 @@ UNPROTECT(1); emit symbolList(vars, values, funcs); - -// setStatus(RServer::Idle); + setStatus(Idle); } void RServer::setStatus(Status status) diff --git a/src/backends/R/rserver/rserver.kcfg b/src/backends/R/rserver/rserver.kcfg --- a/src/backends/R/rserver/rserver.kcfg +++ b/src/backends/R/rserver/rserver.kcfg @@ -12,6 +12,10 @@ true + + + true + diff --git a/src/backends/R/rsession.h b/src/backends/R/rsession.h --- a/src/backends/R/rsession.h +++ b/src/backends/R/rsession.h @@ -28,6 +28,7 @@ #include "rserver_interface.h" class RExpression; +class RVariableModel; class QProcess; namespace Cantor { @@ -49,27 +50,19 @@ 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; + void sendInputToServer(const QString& input); + void updateSymbols(const RVariableModel* model); 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(); - void syntaxRegExpsFilled(); 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/R/rsession.cpp b/src/backends/R/rsession.cpp --- a/src/backends/R/rsession.cpp +++ b/src/backends/R/rsession.cpp @@ -23,6 +23,7 @@ #include "rexpression.h" #include "rcompletionobject.h" #include "rhighlighter.h" +#include "rvariablemodel.h" #include #include @@ -33,8 +34,11 @@ #include #endif -RSession::RSession(Cantor::Backend* backend) : Session(backend), m_process(nullptr), m_rServer(nullptr), m_variableModel(new Cantor::DefaultVariableModel(this)) +RSession::RSession(Cantor::Backend* backend) : Session(backend), +m_process(nullptr), +m_rServer(nullptr) { + setVariableModel(new RVariableModel(this)); } RSession::~RSession() @@ -53,14 +57,14 @@ 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(QStringList,QStringList,QStringList)),this,SLOT(receiveSymbols(QStringList,QStringList,QStringList))); changeStatus(Session::Done); emit loginDone(); @@ -72,9 +76,9 @@ qDebug()<<"logout"; m_process->terminate(); - m_variableModel->clearVariables(); - m_variables.clear(); - m_functions.clear(); + RVariableModel* model = static_cast(variableModel()); + model->clearVariables(); + model->clearFunctions(); emit symbolsChanged(); changeStatus(Status::Disable); @@ -127,55 +131,25 @@ 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())); - connect(this,SIGNAL(syntaxRegExpsFilled()), h, SLOT(updateHighlighting())); + RVariableModel* model = static_cast(variableModel()); + connect(model, &Cantor::DefaultVariableModel::variablesAdded, h, &RHighlighter::addUserVariable); + connect(model, &Cantor::DefaultVariableModel::variablesRemoved, h, &RHighlighter::removeUserVariable); + connect(model, &Cantor::DefaultVariableModel::functionsAdded, h, &RHighlighter::addUserFunction); + connect(model, &Cantor::DefaultVariableModel::functionsRemoved, h, &RHighlighter::removeUserFunction); 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"))); - - emit syntaxRegExpsFilled(); -} - -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()); + RExpression* expr = static_cast(expressionQueue().first()); qDebug()<<"done running "<command(); } - - if(expressionQueue().isEmpty()) - changeStatus(Cantor::Session::Done); - else - runFirstExpression(); + finishFirstExpression(); } else changeStatus(Cantor::Session::Running); @@ -209,7 +183,9 @@ m_rServer->answerRequest(s); } -QAbstractItemModel* RSession::variableModel() +void RSession::updateSymbols(const RVariableModel* model) { - return m_variableModel; + disconnect(m_rServer, SIGNAL(symbolList(QStringList,QStringList,QStringList))); + connect(m_rServer, SIGNAL(symbolList(QStringList,QStringList,QStringList)), model, SLOT(parseResult(QStringList,QStringList,QStringList))); + m_rServer->listSymbols(); } diff --git a/src/backends/python2/testpython2.h b/src/backends/R/rvariablemodel.h copy from src/backends/python2/testpython2.h copy to src/backends/R/rvariablemodel.h --- a/src/backends/python2/testpython2.h +++ b/src/backends/R/rvariablemodel.h @@ -15,26 +15,30 @@ Boston, MA 02110-1301, USA. --- - Copyright (C) 2013 Tuukka Verho - */ + Copyright (C) 2018 Nikita Sirgienko +*/ -#ifndef _TESTPYTHON2_H -#define _TESTPYTHON2_H +#ifndef _RVARIABLEMODEL_H +#define _RVARIABLEMODEL_H -#include "backendtest.h" +#include "defaultvariablemodel.h" +class RSession; -class TestPython2 : public BackendTest +class RVariableModel : public Cantor::DefaultVariableModel { Q_OBJECT - private Q_SLOTS: - void testImportNumpy(); - void testCodeWithComments(); - void testSimpleCode(); - void testMultilineCode(); + public: + RVariableModel( RSession* session); + ~RVariableModel() override = default; + + void update() override; + + public Q_SLOTS: + void parseResult(const QStringList& names, const QStringList& values, const QStringList& funcs); private: - QString backendName() override; + QStringList m_functions; }; -#endif /* _TESTPYTHON2_H */ +#endif /* _RVARIABLEMODEL_H */ diff --git a/src/backends/R/rvariablemodel.cpp b/src/backends/R/rvariablemodel.cpp new file mode 100644 --- /dev/null +++ b/src/backends/R/rvariablemodel.cpp @@ -0,0 +1,49 @@ +/* + 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 "rvariablemodel.h" +#include "rsession.h" + +using namespace Cantor; + +RVariableModel::RVariableModel(RSession* session): + DefaultVariableModel(session) +{ +} + +void RVariableModel::update() +{ + static_cast(session())->updateSymbols(this); +} + +void RVariableModel::parseResult(const QStringList& names, const QStringList& values, const QStringList& funcs) +{ + QList vars; + if (!values.isEmpty()) // Variables management disabled + for (int i = 0; i < names.size(); i++) + vars.append(Variable{names[i], values[i]}); + else + for (int i = 0; i < names.size(); i++) + vars.append(Variable{names[i], QString()}); + + setVariables(vars); + + setFunctions(funcs); +} diff --git a/src/backends/R/settings.ui b/src/backends/R/settings.ui --- a/src/backends/R/settings.ui +++ b/src/backends/R/settings.ui @@ -32,6 +32,16 @@ + + + + Let Cantor follow the creation/destruction of variables + + + Enable Variable Management + + + diff --git a/src/backends/python2/testpython2.h b/src/backends/R/testr.h copy from src/backends/python2/testpython2.h copy to src/backends/R/testr.h --- a/src/backends/python2/testpython2.h +++ b/src/backends/R/testr.h @@ -15,26 +15,25 @@ Boston, MA 02110-1301, USA. --- - Copyright (C) 2013 Tuukka Verho + Copyright (C) 2019 Sirgienko Nikita */ -#ifndef _TESTPYTHON2_H -#define _TESTPYTHON2_H +#ifndef _TESTR_H +#define _TESTR_H #include "backendtest.h" - -class TestPython2 : public BackendTest +class TestR : public BackendTest { Q_OBJECT - private Q_SLOTS: - void testImportNumpy(); - void testCodeWithComments(); - void testSimpleCode(); - void testMultilineCode(); - private: +private Q_SLOTS: + //tests variable model + void testVariablesCreatingFromCode(); + void testVariableCleanupAfterRestart(); + +private: QString backendName() override; }; -#endif /* _TESTPYTHON2_H */ +#endif /* _TESTR_H */ diff --git a/src/backends/R/testr.cpp b/src/backends/R/testr.cpp new file mode 100644 --- /dev/null +++ b/src/backends/R/testr.cpp @@ -0,0 +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) 2019 Sirgienko Nikita + */ + +#include "testr.h" + +#include "session.h" +#include "backend.h" +#include "expression.h" +#include "result.h" +#include "completionobject.h" +#include "defaultvariablemodel.h" + +#include + + +QString TestR::backendName() +{ + return QLatin1String("R"); +} + +void TestR::testVariablesCreatingFromCode() +{ + QAbstractItemModel* model = session()->variableModel(); + QVERIFY(model != nullptr); + + Cantor::Expression* e=evalExp(QLatin1String("a1 = 15; b1 = 'S'; d1 = c(1,2,3)")); + QVERIFY(e!=nullptr); + + while(session()->status() != Cantor::Session::Done) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(3, model->rowCount()); + + QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a1")); + QCOMPARE(model->index(0,1).data().toString(), QLatin1String("15")); + + QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b1")); + QCOMPARE(model->index(1,1).data().toString(), QLatin1String("S")); + + QCOMPARE(model->index(2,0).data().toString(), QLatin1String("d1")); + QCOMPARE(model->index(2,1).data().toString(), QLatin1String("1, 2, 3")); + + evalExp(QLatin1String("rm(a1,b1,d1)")); +} + +void TestR::testVariableCleanupAfterRestart() +{ + Cantor::DefaultVariableModel* model = static_cast(session()->variableModel()); + QVERIFY(model != nullptr); + + while(session()->status() != Cantor::Session::Done) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(static_cast(model)->rowCount(), 0); + + Cantor::Expression* e=evalExp(QLatin1String("h1 = 15; h2 = 'S';")); + QVERIFY(e!=nullptr); + + while(session()->status() != Cantor::Session::Done) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(static_cast(model)->rowCount(), 2); + + session()->logout(); + session()->login(); + + QCOMPARE(static_cast(model)->rowCount(), 0); +} + +QTEST_MAIN( TestR ) + diff --git a/src/backends/julia/CMakeLists.txt b/src/backends/julia/CMakeLists.txt --- a/src/backends/julia/CMakeLists.txt +++ b/src/backends/julia/CMakeLists.txt @@ -7,6 +7,7 @@ juliasession.cpp juliaexpression.cpp juliakeywords.cpp + juliavariablemodel.cpp juliahighlighter.cpp juliaextensions.cpp juliacompletionobject.cpp @@ -26,9 +27,9 @@ if(BUILD_TESTING) add_executable(testjulia testjulia.cpp) add_test(NAME testjulia COMMAND testjulia) - target_link_libraries(testjulia + target_link_libraries(testjulia Qt5::Test - cantorlibs + cantorlibs cantortest ) endif(BUILD_TESTING) diff --git a/src/backends/julia/juliabackend.cpp b/src/backends/julia/juliabackend.cpp --- a/src/backends/julia/juliabackend.cpp +++ b/src/backends/julia/juliabackend.cpp @@ -55,9 +55,14 @@ Cantor::Backend::Capabilities JuliaBackend::capabilities() const { - return Cantor::Backend::SyntaxHighlighting | - Cantor::Backend::VariableManagement | - Cantor::Backend::Completion; + Cantor::Backend::Capabilities cap= + SyntaxHighlighting| + Completion; + + if (JuliaSettings::variableManagement()) + cap |= VariableManagement; + + return cap; } QString JuliaBackend::description() const diff --git a/src/backends/julia/juliabackend.kcfg b/src/backends/julia/juliabackend.kcfg --- a/src/backends/julia/juliabackend.kcfg +++ b/src/backends/julia/juliabackend.kcfg @@ -12,6 +12,10 @@ QUrl::fromLocalFile(QStandardPaths::findExecutable(QLatin1String("julia"))) + + + true + true diff --git a/src/backends/julia/juliahighlighter.h b/src/backends/julia/juliahighlighter.h --- a/src/backends/julia/juliahighlighter.h +++ b/src/backends/julia/juliahighlighter.h @@ -42,10 +42,11 @@ ~JuliaHighlighter() override = default; public Q_SLOTS: - /** - * Call this to update highlighter to the current state of keywords storage - */ - void updateHighlight(); + void addUserVariable(const QStringList& variables); + void removeUserVariable(const QStringList& variables); + + void addUserFunctions(const QStringList functions); + void removeUserFunctions(const QStringList functions); protected: /** diff --git a/src/backends/julia/juliahighlighter.cpp b/src/backends/julia/juliahighlighter.cpp --- a/src/backends/julia/juliahighlighter.cpp +++ b/src/backends/julia/juliahighlighter.cpp @@ -163,20 +163,24 @@ setCurrentBlockState(state); } -void JuliaHighlighter::updateHighlight() +void JuliaHighlighter::addUserVariable(const QStringList& variables) { - // Remove rules for outdated variables and functions - for (const auto &var : JuliaKeywords::instance()->removedVariables()) { - removeRule(var); - } - for (const auto &func : JuliaKeywords::instance()->removedFunctions()) { - removeRule(func); - } + addVariables(variables); +} - // Add actual variables and function - addVariables(JuliaKeywords::instance()->variables()); - addFunctions(JuliaKeywords::instance()->functions()); - rehighlight(); +void JuliaHighlighter::removeUserVariable(const QStringList& variables) +{ + removeRules(variables); +} + +void JuliaHighlighter::addUserFunctions(const QStringList functions) +{ + addFunctions(functions); +} + +void JuliaHighlighter::removeUserFunctions(const QStringList functions) +{ + removeRules(functions); } QString JuliaHighlighter::nonSeparatingCharacters() const diff --git a/src/backends/julia/juliaserver/juliaserver.h b/src/backends/julia/juliaserver/juliaserver.h --- a/src/backends/julia/juliaserver/juliaserver.h +++ b/src/backends/julia/juliaserver/juliaserver.h @@ -75,8 +75,10 @@ /** * Reparse internal julia module and update list of variables and functions + * + * @param variableManagement true, if Variable Management enabled for this session */ - Q_SCRIPTABLE void parseModules(); + Q_SCRIPTABLE void parseModules(bool variableManagement); /** * @return list of variables in internal Julia's module @@ -94,7 +96,7 @@ Q_SCRIPTABLE QStringList functionsList(); private: - void parseJlModule(jl_module_t* module); + void parseJlModule(jl_module_t* module, bool parseValue); QString fromJuliaString(const jl_value_t* value); private: diff --git a/src/backends/julia/juliaserver/juliaserver.cpp b/src/backends/julia/juliaserver/juliaserver.cpp --- a/src/backends/julia/juliaserver/juliaserver.cpp +++ b/src/backends/julia/juliaserver/juliaserver.cpp @@ -148,12 +148,12 @@ return m_was_exception; } -void JuliaServer::parseModules() +void JuliaServer::parseModules(bool variableManagement) { - parseJlModule(jl_internal_main_module); + parseJlModule(jl_internal_main_module, variableManagement); } -void JuliaServer::parseJlModule(jl_module_t* module) +void JuliaServer::parseJlModule(jl_module_t* module, bool parseValue) { jl_function_t* jl_string_function = jl_get_function(jl_base_module, "string"); @@ -183,7 +183,7 @@ if (jl_is_module(value)) { if (module == jl_internal_main_module && (jl_module_t*)value != jl_internal_main_module) - parseJlModule((jl_module_t*)value); + parseJlModule((jl_module_t*)value, parseValue); } // Function else if (type.startsWith(QLatin1String("#")) || type == QLatin1String("Function")) @@ -196,16 +196,24 @@ { if (module == jl_internal_main_module && !INTERNAL_VARIABLES.contains(name)) { - const QString& valueString = fromJuliaString(jl_call1(jl_string_function, value)); - if (m_variables.contains(name)) + if (parseValue) { - int i = m_variables.indexOf(name); - m_variableValues[i] = valueString; + const QString& valueString = fromJuliaString(jl_call1(jl_string_function, value)); + if (m_variables.contains(name)) + { + int i = m_variables.indexOf(name); + m_variableValues[i] = valueString; + } + else + { + m_variables.append(name); + m_variableValues.append(valueString); + } } else { - m_variables.append(name); - m_variableValues.append(valueString); + if (!m_variables.contains(name)) + m_variables.append(name); } } } diff --git a/src/backends/julia/juliasession.h b/src/backends/julia/juliasession.h --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -26,6 +26,7 @@ class JuliaExpression; class JuliaCompletionObject; +class JuliaVariableModel; class KProcess; class QDBusInterface; namespace Cantor { @@ -93,12 +94,6 @@ */ bool integratePlots(); -Q_SIGNALS: - /** - * Emit this to update syntax highlighter - */ - void updateHighlighter(); - private Q_SLOTS: /** * Called when async call to JuliaServer is finished @@ -114,8 +109,8 @@ JuliaExpression *m_currentExpression; //< current expression /// Variable management model - Cantor::DefaultVariableModel *m_variableModel; - static const QRegularExpression typeVariableInfo; + JuliaVariableModel *m_variableModel; + bool m_needUpdate; /// Cache to speedup modules whos calls QMap m_whos_cache; @@ -167,9 +162,4 @@ * @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/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp --- a/src/backends/julia/juliasession.cpp +++ b/src/backends/julia/juliasession.cpp @@ -31,19 +31,21 @@ #include "settings.h" #include "juliahighlighter.h" #include "juliakeywords.h" +#include "juliavariablemodel.h" #include "juliaextensions.h" #include "juliabackend.h" #include "juliacompletionobject.h" #include -const QRegularExpression JuliaSession::typeVariableInfo = QRegularExpression(QLatin1String("\\w+\\[")); +using namespace Cantor; JuliaSession::JuliaSession(Cantor::Backend *backend) : Session(backend) , m_process(nullptr) , m_interface(nullptr) , m_currentExpression(nullptr) - , m_variableModel(new Cantor::DefaultVariableModel(this)) + , m_variableModel(new JuliaVariableModel(this)) + , m_needUpdate(false) { } @@ -101,7 +103,8 @@ JuliaSettings::self()->replPath().path() ); - listVariables(); + m_variableModel->setJuliaServer(m_interface); + m_variableModel->update(); // Plots integration if (integratePlots()) { @@ -119,10 +122,7 @@ { m_process->terminate(); - JuliaKeywords::instance()->clearVariables(); - JuliaKeywords::instance()->clearFunctions(); m_variableModel->clearVariables(); - emit updateHighlighter(); changeStatus(Status::Disable); } @@ -167,9 +167,12 @@ QSyntaxHighlighter *JuliaSession::syntaxHighlighter(QObject *parent) { JuliaHighlighter *highlighter = new JuliaHighlighter(parent); - QObject::connect( - this, SIGNAL(updateHighlighter()), highlighter, SLOT(updateHighlight()) - ); + + connect( m_variableModel, &DefaultVariableModel::variablesAdded, highlighter, &JuliaHighlighter::addUserVariable); + connect( m_variableModel, &DefaultVariableModel::variablesRemoved, highlighter, &JuliaHighlighter::removeUserVariable); + connect( m_variableModel, &DefaultVariableModel::functionsAdded, highlighter, &JuliaHighlighter::addUserFunctions); + connect( m_variableModel, &DefaultVariableModel::functionsRemoved, highlighter, &JuliaHighlighter::removeUserFunctions); + return highlighter; } @@ -191,9 +194,14 @@ void JuliaSession::onResultReady() { m_currentExpression->finalize(); + m_needUpdate |= !m_currentExpression->isInternal(); m_runningExpressions.removeAll(m_currentExpression); - listVariables(); + if(m_needUpdate) + { + m_variableModel->update(); + m_needUpdate = false; + } changeStatus(Cantor::Session::Done); } @@ -228,50 +236,6 @@ 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, something 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; diff --git a/src/backends/python/pythonserver.h b/src/backends/julia/juliavariablemodel.h copy from src/backends/python/pythonserver.h copy to src/backends/julia/juliavariablemodel.h --- a/src/backends/python/pythonserver.h +++ b/src/backends/julia/juliavariablemodel.h @@ -15,33 +15,36 @@ Boston, MA 02110-1301, USA. --- - Copyright (C) 2015 Minh Ngo - */ + Copyright (C) 2019 Nikita Sirgienko +*/ -#ifndef _PYTHONSERVER_H -#define _PYTHONSERVER_H -#include -#include +#ifndef _PYTHONVARIABLEMODEL_H +#define _PYTHONVARIABLEMODEL_H -struct _object; -using PyObject = _object; +#include -class PythonServer : public QObject +#include "defaultvariablemodel.h" + +class JuliaSession; +class QDBusInterface; + +class JuliaVariableModel : public Cantor::DefaultVariableModel { Q_OBJECT public: - explicit PythonServer(QObject* parent = nullptr); + JuliaVariableModel( JuliaSession* session); + ~JuliaVariableModel() override = default; + + void update() override; - public Q_SLOTS: - Q_SCRIPTABLE void login(); - Q_SCRIPTABLE void setFilePath(const QString& path); - Q_SCRIPTABLE void runPythonCommand(const QString& command) const; - Q_SCRIPTABLE QString getOutput() const; - Q_SCRIPTABLE QString getError() const; + void setJuliaServer(QDBusInterface* interface); + + private: + static const QRegularExpression typeVariableInfo; private: - PyObject* m_pModule; - QString filePath; + QDBusInterface* m_interface; + QStringList m_functions; }; -#endif +#endif /* _PYTHONVARIABLEMODEL_H */ diff --git a/src/backends/julia/juliavariablemodel.cpp b/src/backends/julia/juliavariablemodel.cpp new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliavariablemodel.cpp @@ -0,0 +1,93 @@ +/* + 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 "juliavariablemodel.h" +#include "juliaextensions.h" +#include "juliasession.h" + +#include +#include +#include +#include + +#include "settings.h" + +using namespace Cantor; + +const QRegularExpression JuliaVariableModel::typeVariableInfo = QRegularExpression(QLatin1String("\\w+\\[")); + +JuliaVariableModel::JuliaVariableModel(JuliaSession* session): + DefaultVariableModel(session), + m_interface(nullptr) +{ +} + +void JuliaVariableModel::setJuliaServer(QDBusInterface* interface) +{ + m_interface = interface; +} + +void JuliaVariableModel::update() +{ + if (!m_interface) + return; + + m_interface->call(QLatin1String("parseModules"), JuliaSettings::variableManagement()); + + const QStringList& variables = + static_cast>(m_interface->call(QLatin1String("variablesList"))).value(); + + QList vars; + if (JuliaSettings::variableManagement()) + { + 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, something 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 + vars << Variable{name, value.replace(typeVariableInfo,QLatin1String("["))}; + } + } + } + else + { + for (int i = 0; i < variables.size(); i++) + vars << Variable{variables[i], QString()}; + } + setVariables(vars); + + const QStringList& newFuncs = + static_cast>(m_interface->call(QLatin1String("functionsList"))).value(); + setFunctions(newFuncs); +} diff --git a/src/backends/julia/settings.ui b/src/backends/julia/settings.ui --- a/src/backends/julia/settings.ui +++ b/src/backends/julia/settings.ui @@ -16,6 +16,16 @@ + + + + + Let Cantor follow the creation/destruction of variables + + + Enable Variable Management + + diff --git a/src/backends/maxima/maximasession.h b/src/backends/maxima/maximasession.h --- a/src/backends/maxima/maximasession.h +++ b/src/backends/maxima/maximasession.h @@ -50,7 +50,6 @@ Cantor::CompletionObject* completionFor(const QString& command, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& command) override; QSyntaxHighlighter* syntaxHighlighter(QObject*) override; - QAbstractItemModel* variableModel() override; void runFirstExpression() override; public Q_SLOTS: @@ -68,7 +67,6 @@ QProcess* m_process; QString m_cache; - MaximaVariableModel* m_variableModel; bool m_justRestarted; }; diff --git a/src/backends/maxima/maximasession.cpp b/src/backends/maxima/maximasession.cpp --- a/src/backends/maxima/maximasession.cpp +++ b/src/backends/maxima/maximasession.cpp @@ -49,9 +49,9 @@ MaximaSession::MaximaSession( Cantor::Backend* backend ) : Session(backend), m_process(nullptr), - m_variableModel(new MaximaVariableModel(this)), m_justRestarted(false) { + setVariableModel(new MaximaVariableModel(this)); } void MaximaSession::login() @@ -89,6 +89,7 @@ QString autorunScripts = MaximaSettings::self()->autorunScripts().join(QLatin1String(";")); autorunScripts.append(QLatin1String(";kill(labels)")); // Reset labels after running autorun scripts evaluateExpression(autorunScripts, MaximaExpression::DeleteOnFinish, true); + forceVariableUpdate(); } changeStatus(Session::Done); @@ -122,7 +123,9 @@ expressionQueue().clear(); delete m_process; m_process = nullptr; - m_variableModel->clear(); + MaximaVariableModel* model = static_cast(variableModel()); + model->clearVariables(); + model->clearFunctions(); changeStatus(Status::Disable); @@ -194,29 +197,21 @@ { Cantor::Expression* expression = expressionQueue().first(); const QString& cmd = expression->command(); - bool isInternal = expression->isInternal(); qDebug() << "expression status changed: command = " << expression->command() << ", status = " << status; - if(status!=Cantor::Expression::Computing) //The session is ready for the next command + switch (status) { + case Cantor::Expression::Done: + case Cantor::Expression::Error: qDebug()<<"################################## EXPRESSION END ###############################################"; disconnect(expression, SIGNAL(statusChanged(Cantor::Expression::Status)), - this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); + this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); - expressionQueue().removeFirst(); - if(expressionQueue().isEmpty()) - { - //if we are done with all the commands in the queue, - //use the opportunity to update the variablemodel (if the last command wasn't already an update, as infinite loops aren't fun) - - if(!isInternal || !m_variableModel->isUpdateCommand(cmd)) - m_variableModel->update(); - else - changeStatus(Cantor::Session::Done); - }else - { - runFirstExpression(); - } + finishFirstExpression(); + break; + + default: + break; } } @@ -331,11 +326,6 @@ 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; diff --git a/src/backends/maxima/maximavariablemodel.h b/src/backends/maxima/maximavariablemodel.h --- a/src/backends/maxima/maximavariablemodel.h +++ b/src/backends/maxima/maximavariablemodel.h @@ -36,35 +36,18 @@ explicit MaximaVariableModel( MaximaSession* session); ~MaximaVariableModel() override = default; - void clear(); - - QList variables(); - QList functions(); - - QStringList variableNames(); QStringList functionNames(bool stripParameters=false); - bool isUpdateCommand(const QString &cmd) const; - public Q_SLOTS: - void update(); + void update() override; private Q_SLOTS: - void parseNewVariables(Cantor::Expression::Status status); - void parseNewFunctions(Cantor::Expression::Status status); - - Q_SIGNALS: - void variablesAdded(const QStringList variables); - void variablesRemoved(const QStringList variables); - - void functionsAdded(const QStringList variables); - void functionsRemoved(const QStringList variables); + void parseNewVariables(Cantor::Expression::Status status); + void parseNewFunctions(Cantor::Expression::Status status); private: MaximaSession* maximaSession(); private: - QList m_variables; - QList m_functions; Cantor::Expression* m_variableExpression; Cantor::Expression* m_functionExpression; }; diff --git a/src/backends/maxima/maximavariablemodel.cpp b/src/backends/maxima/maximavariablemodel.cpp --- a/src/backends/maxima/maximavariablemodel.cpp +++ b/src/backends/maxima/maximavariablemodel.cpp @@ -28,39 +28,43 @@ #include #include +#include "settings.h" + //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::clear() +MaximaVariableModel::MaximaVariableModel( MaximaSession* session) : Cantor::DefaultVariableModel(session), +m_variableExpression(nullptr), +m_functionExpression(nullptr) { - emit functionsRemoved(functionNames()); - emit variablesRemoved(variableNames()); - m_functions.clear(); - m_variables.clear(); - DefaultVariableModel::clearVariables(); } void MaximaVariableModel::update() { - qDebug()<<"checking for new variables"; - const QString& cmd1=variableInspectCommand.arg(QLatin1String("values")); - Cantor::Expression* expr1=session()->evaluateExpression(cmd1, Cantor::Expression::FinishingBehavior::DoNotDelete, true); - connect(expr1, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewVariables); + if (!m_variableExpression) + { + qDebug()<<"checking for new variables"; + const QString& cmd1=variableInspectCommand.arg(QLatin1String("values")); + m_variableExpression = session()->evaluateExpression(cmd1, Cantor::Expression::FinishingBehavior::DoNotDelete, true); + connect(m_variableExpression, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewVariables); + } - qDebug()<<"checking for new functions"; - const QString& cmd2=inspectCommand.arg(QLatin1String("functions")); - Cantor::Expression* expr2=session()->evaluateExpression(cmd2, Cantor::Expression::FinishingBehavior::DoNotDelete, true); - connect(expr2, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewFunctions); + if (!m_functionExpression) + { + qDebug()<<"checking for new functions"; + const QString& cmd2=inspectCommand.arg(QLatin1String("functions")); + m_functionExpression = session()->evaluateExpression(cmd2, Cantor::Expression::FinishingBehavior::DoNotDelete, true); + connect(m_functionExpression, &Cantor::Expression::statusChanged, this, &MaximaVariableModel::parseNewFunctions); + } } QList parse(MaximaExpression* expr) { - if(!expr || expr->status()!=Cantor::Expression::Done || expr->results().isEmpty()) { + if(!expr + || (expr->status()!=Cantor::Expression::Done && expr->errorMessage() != QLatin1String("$DONE")) + || expr->results().isEmpty()) + { return QList(); } @@ -103,10 +107,13 @@ //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(); + if (MaximaSettings::self()->variableManagement()) + { + 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()<(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< newVars=parse(static_cast(m_variableExpression)); + setVariables(newVars); //the expression is not needed anymore - expr->deleteLater(); - - emit variablesAdded(addedVars); - emit variablesRemoved(removedVars); + m_variableExpression->deleteLater(); + m_variableExpression = nullptr; } void MaximaVariableModel::parseNewFunctions(Cantor::Expression::Status status) { if (status != Cantor::Expression::Done && status != Cantor::Expression::Error) return; qDebug()<<"parsing functions"; - MaximaExpression* expr=static_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< newFuncs=parse(static_cast(m_functionExpression)); + QStringList functions; + for (Variable var : newFuncs) + functions << var.name; + setFunctions(functions); //the expression is not needed anymore - expr->deleteLater(); - - emit functionsAdded(addedVars); - emit functionsRemoved(removedVars); -} - -bool MaximaVariableModel::isUpdateCommand(const QString& cmd) const -{ - return cmd == variableInspectCommand.arg(QLatin1String("values")) - || cmd == inspectCommand.arg(QLatin1String("functions")); + m_functionExpression->deleteLater(); + m_functionExpression = nullptr; } 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<variableManagement()) + cap |= VariableManagement; + return cap; } Cantor::Session* OctaveBackend::createSession() diff --git a/src/backends/octave/octavebackend.kcfg b/src/backends/octave/octavebackend.kcfg --- a/src/backends/octave/octavebackend.kcfg +++ b/src/backends/octave/octavebackend.kcfg @@ -17,6 +17,10 @@ true + + + true + 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 @@ -117,28 +117,6 @@ } } - // 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); diff --git a/src/backends/octave/octavehighlighter.h b/src/backends/octave/octavehighlighter.h --- a/src/backends/octave/octavehighlighter.h +++ b/src/backends/octave/octavehighlighter.h @@ -37,12 +37,8 @@ ~OctaveHighlighter() override = default; public Q_SLOTS: - void updateVariables(); - void sessionStatusChanged(Cantor::Session::Status status); - - private: - Cantor::Session* m_session; - QStringList m_variables; + void addUserVariable(const QStringList& variables); + void removeUserVariable(const QStringList& variables); }; #endif // OCTAVEHIGHLIGHTER_H diff --git a/src/backends/octave/octavehighlighter.cpp b/src/backends/octave/octavehighlighter.cpp --- a/src/backends/octave/octavehighlighter.cpp +++ b/src/backends/octave/octavehighlighter.cpp @@ -23,10 +23,11 @@ #include #include +#include "octavesession.h" using namespace Cantor; -OctaveHighlighter::OctaveHighlighter(QObject* parent, Session* session): DefaultHighlighter(parent), m_session(session) +OctaveHighlighter::OctaveHighlighter(QObject* parent, Session* session): DefaultHighlighter(parent) { addKeywords(OctaveKeywords::instance()->keywords()); addFunctions(OctaveKeywords::instance()->functions()); @@ -48,48 +49,12 @@ rehighlight(); } -void OctaveHighlighter::updateVariables() +void OctaveHighlighter::addUserVariable(const QStringList& variables) { - Expression* expr = m_session->evaluateExpression(QLatin1String("who"), Expression::FinishingBehavior::DoNotDelete, true); - connect(expr, &Expression::statusChanged, [this, expr](Expression::Status status) - { - if (status != Expression::Done && status != Expression::Error) - return; - - if (status == Expression::Done && expr->result()) - { - QString res = expr->result()->toHtml(); - res.replace(QLatin1String("
"),QLatin1String(" ")); - res.remove(0, res.indexOf(QLatin1Char('\n'))); - res.remove(QLatin1Char('\n')); - res = res.trimmed(); - - QStringList newVariables; - foreach ( const QString& var, res.split(QLatin1Char(' '), QString::SkipEmptyParts)) - { - newVariables << var.trimmed(); - } - qDebug() << "Received" << newVariables.size() << "variables"; - - for (const QString& newVariable: newVariables) - if (!m_variables.contains(newVariable)) - addRule(newVariable, variableFormat()); - - for (const QString& variable: m_variables) - if (!newVariables.contains(variable)) - removeRule(variable); - - m_variables = std::move(newVariables); - rehighlight(); - } - expr->deleteLater(); - }); + addVariables(variables); } -void OctaveHighlighter::sessionStatusChanged(Cantor::Session::Status status) +void OctaveHighlighter::removeUserVariable(const QStringList& variables) { - if (status == Cantor::Session::Status::Disable) - for (const QString& variable: m_variables) - removeRule(variable); - rehighlight(); + removeRules(variables); } 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 @@ -48,12 +48,8 @@ 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; @@ -70,8 +66,6 @@ QString m_output; - Cantor::DefaultVariableModel* m_variableModel; - private: void readFromOctave(QByteArray data); bool isDoNothingCommand(const QString& command); 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 @@ -40,19 +40,19 @@ #include #endif -#include +#include "octavevariablemodel.h" const QRegExp OctaveSession::PROMPT_UNCHANGEABLE_COMMAND = QRegExp(QLatin1String("(,|;)+")); -OctaveSession::OctaveSession ( Cantor::Backend* backend ) : Session ( backend ), +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)) +m_syntaxError(false) { + setVariableModel(new OctaveVariableModel(this)); qDebug() << octaveScriptInstallDir; } @@ -142,6 +142,7 @@ QString autorunScripts = OctaveSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, OctaveExpression::DeleteOnFinish, true); + forceVariableUpdate(); } changeStatus(Cantor::Session::Done); @@ -181,7 +182,8 @@ m_output.clear(); m_previousPromptNumber = 1; - m_variableModel->clearVariables(); + OctaveVariableModel* model = static_cast(variableModel()); + model->clearVariables(); changeStatus(Status::Disable); @@ -284,13 +286,7 @@ 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 { @@ -322,12 +318,9 @@ { case Cantor::Expression::Done: case Cantor::Expression::Error: - expressionQueue().removeFirst(); - if (expressionQueue().isEmpty()) - changeStatus(Done); - else - runFirstExpression(); + finishFirstExpression(); break; + default: break; } @@ -360,18 +353,13 @@ { 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)) ); + OctaveVariableModel* model = static_cast(variableModel()); + connect (model, &Cantor::DefaultVariableModel::variablesAdded, highlighter, &OctaveHighlighter::addUserVariable); + connect (model, &Cantor::DefaultVariableModel::variablesRemoved, highlighter, &OctaveHighlighter::removeUserVariable); return highlighter; } -QAbstractItemModel* OctaveSession::variableModel() -{ - return m_variableModel; -} - - void OctaveSession::runSpecificCommands() { m_process->write("figure(1,'visible','off')"); diff --git a/src/backends/python2/testpython2.h b/src/backends/octave/octavevariablemodel.h copy from src/backends/python2/testpython2.h copy to src/backends/octave/octavevariablemodel.h --- a/src/backends/python2/testpython2.h +++ b/src/backends/octave/octavevariablemodel.h @@ -15,26 +15,29 @@ Boston, MA 02110-1301, USA. --- - Copyright (C) 2013 Tuukka Verho - */ + Copyright (C) 2018 Nikita Sirgienko +*/ -#ifndef _TESTPYTHON2_H -#define _TESTPYTHON2_H +#ifndef _OCTAVEVARIABLEMODEL_H +#define _OCTAVEVARIABLEMODEL_H -#include "backendtest.h" +#include "defaultvariablemodel.h" +class OctaveSession; -class TestPython2 : public BackendTest +class OctaveVariableModel : public Cantor::DefaultVariableModel { - Q_OBJECT + public: + OctaveVariableModel( OctaveSession* session); + ~OctaveVariableModel() override = default; + + void update() override; + private Q_SLOTS: - void testImportNumpy(); - void testCodeWithComments(); - void testSimpleCode(); - void testMultilineCode(); + void parseNewVariables(Cantor::Expression::Status status); private: - QString backendName() override; + Cantor::Expression* m_expr; }; -#endif /* _TESTPYTHON2_H */ +#endif /* _OCTAVEVARIABLEMODEL_H */ diff --git a/src/backends/octave/octavevariablemodel.cpp b/src/backends/octave/octavevariablemodel.cpp new file mode 100644 --- /dev/null +++ b/src/backends/octave/octavevariablemodel.cpp @@ -0,0 +1,92 @@ +/* + 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 + +#include "settings.h" + +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(); + const QStringList& lines = text.split(delimiter, QString::SkipEmptyParts); + + QList vars; + for (QString line : lines) + { + line = line.trimmed(); + const QString& name = line.section(QLatin1String("\n"), 0, 0); + QString value; + if (OctaveSettings::self()->variableManagement()) + value = line.section(QLatin1String("\n"), 1); + vars << Variable{name, value}; + } + + setVariables(vars); + break; + } + default: + return; + } + + m_expr->deleteLater(); + m_expr = nullptr; +} diff --git a/src/backends/octave/settings.ui b/src/backends/octave/settings.ui --- a/src/backends/octave/settings.ui +++ b/src/backends/octave/settings.ui @@ -46,6 +46,16 @@
+ + + + Let Cantor follow the creation/destruction of variables + + + Enable Variable Management + + + diff --git a/src/backends/octave/testoctave.h b/src/backends/octave/testoctave.h --- a/src/backends/octave/testoctave.h +++ b/src/backends/octave/testoctave.h @@ -52,6 +52,10 @@ void testCompletion(); + //tests variable model + void testVariablesCreatingFromCode(); + void testVariableCleanupAfterRestart(); + //tests doing a plot void testPlot(); diff --git a/src/backends/octave/testoctave.cpp b/src/backends/octave/testoctave.cpp --- a/src/backends/octave/testoctave.cpp +++ b/src/backends/octave/testoctave.cpp @@ -28,6 +28,7 @@ #include "textresult.h" #include "epsresult.h" #include "completionobject.h" +#include "defaultvariablemodel.h" #include "octaveexpression.h" @@ -145,6 +146,48 @@ QVERIFY(completions.contains(QLatin1String("assert"))); } +void TestOctave::testVariablesCreatingFromCode() +{ + QAbstractItemModel* model = session()->variableModel(); + QVERIFY(model != nullptr); + + evalExp(QLatin1String("clear();")); + + Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); + QVERIFY(e!=nullptr); + + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(2, model->rowCount()); + + QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a")); + QCOMPARE(model->index(0,1).data().toString(), QLatin1String(" 15")); + + QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b")); + QCOMPARE(model->index(1,1).data().toString(), QLatin1String("S")); +} + +void TestOctave::testVariableCleanupAfterRestart() +{ + Cantor::DefaultVariableModel* model = static_cast(session()->variableModel()); + QVERIFY(model != nullptr); + + evalExp(QLatin1String("clear();")); + Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); + QVERIFY(e!=nullptr); + + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(2, static_cast(model)->rowCount()); + + session()->logout(); + session()->login(); + + QCOMPARE(0, static_cast(model)->rowCount()); +} + void TestOctave::testPlot() { Cantor::Expression* e=evalExp( QLatin1String("cantor_plot2d('sin(x)', 'x', -10,10);") ); diff --git a/src/backends/python/CMakeLists.txt b/src/backends/python/CMakeLists.txt --- a/src/backends/python/CMakeLists.txt +++ b/src/backends/python/CMakeLists.txt @@ -3,6 +3,7 @@ pythonsession.cpp pythonexpression.cpp pythonkeywords.cpp + pythonvariablemodel.cpp pythonhighlighter.cpp pythoncompletionobject.cpp pythonextensions.cpp diff --git a/src/backends/python/pythonbackend.h b/src/backends/python/pythonbackend.h --- a/src/backends/python/pythonbackend.h +++ b/src/backends/python/pythonbackend.h @@ -31,10 +31,10 @@ explicit PythonBackend(QObject* parent = nullptr, const QList& args = QList()); ~PythonBackend() override; - Cantor::Backend::Capabilities capabilities() const override; - QWidget* settingsWidget(QWidget* parent) const override; + KConfigSkeleton* config() const override = 0; + Cantor::Backend::Capabilities capabilities() const override = 0; }; diff --git a/src/backends/python/pythonbackend.cpp b/src/backends/python/pythonbackend.cpp --- a/src/backends/python/pythonbackend.cpp +++ b/src/backends/python/pythonbackend.cpp @@ -42,16 +42,6 @@ qDebug()<<"Destroying PythonBackend"; } -Cantor::Backend::Capabilities PythonBackend::capabilities() const -{ - qDebug()<<"Requesting capabilities of PythonSession"; - - return Cantor::Backend::SyntaxHighlighting | - Cantor::Backend::Completion | - Cantor::Backend::SyntaxHelp | - Cantor::Backend::VariableManagement; -} - QWidget* PythonBackend::settingsWidget(QWidget* parent) const { QWidget* widget=new QWidget(parent); diff --git a/src/backends/python/pythoncompletionobject.cpp b/src/backends/python/pythoncompletionobject.cpp --- a/src/backends/python/pythoncompletionobject.cpp +++ b/src/backends/python/pythoncompletionobject.cpp @@ -99,6 +99,7 @@ { if (!m_expression) return; + switch(status) { case Cantor::Expression::Error: diff --git a/src/backends/python/pythonhighlighter.h b/src/backends/python/pythonhighlighter.h --- a/src/backends/python/pythonhighlighter.h +++ b/src/backends/python/pythonhighlighter.h @@ -32,18 +32,15 @@ ~PythonHighlighter() override = default; public Q_SLOTS: - void updateHighlight(); - void addVariable(const QString variable); - void clearVariables(); + void addUserVariable(const QStringList& variables); + void removeUserVariable(const QStringList& variables); protected: void highlightBlock(const QString& text) override; private: QRegExp commentStartExpression; QRegExp commentEndExpression; - QStringList m_variables; - }; #endif /* _PYTHONHIGHLIGHTER_H */ diff --git a/src/backends/python/pythonhighlighter.cpp b/src/backends/python/pythonhighlighter.cpp --- a/src/backends/python/pythonhighlighter.cpp +++ b/src/backends/python/pythonhighlighter.cpp @@ -143,20 +143,12 @@ setCurrentBlockState(state); } -void PythonHighlighter::updateHighlight() +void PythonHighlighter::addUserVariable(const QStringList& variables) { - addVariables(m_variables); - rehighlight(); + addVariables(variables); } -void PythonHighlighter::addVariable(const QString variable) +void PythonHighlighter::removeUserVariable(const QStringList& variables) { - m_variables << variable; -} - -void PythonHighlighter::clearVariables() -{ - removeRules(m_variables); - m_variables.clear(); - rehighlight(); + removeRules(variables); } diff --git a/src/backends/python/pythonserver.h b/src/backends/python/pythonserver.h --- a/src/backends/python/pythonserver.h +++ b/src/backends/python/pythonserver.h @@ -38,6 +38,7 @@ Q_SCRIPTABLE void runPythonCommand(const QString& command) const; Q_SCRIPTABLE QString getOutput() const; Q_SCRIPTABLE QString getError() const; + Q_SCRIPTABLE QString variables(bool parseValue) const; private: PyObject* m_pModule; diff --git a/src/backends/python/pythonserver.cpp b/src/backends/python/pythonserver.cpp --- a/src/backends/python/pythonserver.cpp +++ b/src/backends/python/pythonserver.cpp @@ -144,4 +144,62 @@ PyRun_SimpleString(("__file__ = '"+path.toStdString()+"'").c_str()); } +QString PythonServer::variables(bool parseValue) const +{ + // FIXME: This code allows get full form of numpy array, but for big arrays it's could cause performonce problems + // especially for displaying in variables panel + // So, uncomment this, when fix this problem + /* + "try: \n" + " import numpy \n" + " __cantor_numpy_internal__ = numpy.get_printoptions()['threshold'] \n" + " numpy.set_printoptions(threshold=100000000) \n" + "except ModuleNotFoundError: \n" + " pass \n" + + "try: \n" + " import numpy \n" + " numpy.set_printoptions(threshold=__cantor_numpy_internal__) \n" + " del __cantor_numpy_internal__ \n" + "except ModuleNotFoundError: \n" + " pass \n" + */ + + PyRun_SimpleString("__tmp_globals__ = globals()"); + PyObject* globals = PyObject_GetAttrString(m_pModule,"__tmp_globals__"); + PyObject *key, *value; + Py_ssize_t pos = 0; + + QStringList vars; + const QChar sep(30); // INFORMATION SEPARATOR TWO + while (PyDict_Next(globals, &pos, &key, &value)) { + const QString& keyString = pyObjectToQString(key); + if (keyString.startsWith(QLatin1String("__"))) + continue; + + if (keyString == QLatin1String("CatchOutPythonBackend") + || keyString == QLatin1String("errorPythonBackend") + || keyString == QLatin1String("outputPythonBackend")) + continue; + + if (PyModule_Check(value)) + continue; + + if (PyFunction_Check(value)) + continue; + + if (PyType_Check(value)) + continue; + + QString valueString; + if (parseValue) + valueString = pyObjectToQString(PyObject_Repr(value)); + + + vars.append(keyString + QChar(31) + valueString); + } + + return vars.join(sep); +} + diff --git a/src/backends/python/pythonsession.h b/src/backends/python/pythonsession.h --- a/src/backends/python/pythonsession.h +++ b/src/backends/python/pythonsession.h @@ -26,11 +26,8 @@ #include #include -namespace Cantor { -class DefaultVariableModel; -} - class PythonExpression; +class PythonVariableModel; class KDirWatch; class QDBusInterface; class KProcess; @@ -56,9 +53,10 @@ virtual bool integratePlots() const = 0; virtual QStringList autorunScripts() const = 0; + virtual bool variableManagement() const = 0; private: - Cantor::DefaultVariableModel* m_variableModel; + PythonVariableModel* m_variableModel; QList m_runningExpressions; PythonExpression* m_currentExpression; @@ -71,13 +69,13 @@ QString worksheetPath; int m_pythonVersion; + bool m_needUpdate; + protected: QString m_output; QString m_error; private: - void listVariables(); - void getPythonCommandOutput(const QString& commandProcessing); QString identifyPythonModule(const QString& command) const; @@ -96,11 +94,6 @@ private Q_SLOTS: void readOutput(const QString& commandProcessing); void expressionFinished(); - - Q_SIGNALS: - void updateHighlighter(); - void newVariable(const QString variable); - void clearVariables(); }; #endif /* _PYTHONSESSION_H */ diff --git a/src/backends/python/pythonsession.cpp b/src/backends/python/pythonsession.cpp --- a/src/backends/python/pythonsession.cpp +++ b/src/backends/python/pythonsession.cpp @@ -21,6 +21,7 @@ #include "pythonsession.h" #include "pythonexpression.h" +#include "pythonvariablemodel.h" #include "pythonhighlighter.h" #include "pythoncompletionobject.h" #include "pythonkeywords.h" @@ -43,13 +44,14 @@ PythonSession::PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName, const QString DbusChannelName) : Session(backend) - , m_variableModel(new Cantor::DefaultVariableModel(this)) + , m_variableModel(new PythonVariableModel(this)) , m_currentExpression(nullptr) , m_pIface(nullptr) , m_pProcess(nullptr) , serverName(serverName) , DbusChannelName(DbusChannelName) , m_pythonVersion(pythonVersion) + , m_needUpdate(false) { } @@ -98,21 +100,22 @@ return; } + m_variableModel->setPythonServer(m_pIface); + m_pIface->call(QString::fromLatin1("login")); m_pIface->call(QString::fromLatin1("setFilePath"), worksheetPath); const QStringList& scripts = autorunScripts(); if(!scripts.isEmpty()){ QString autorunScripts = scripts.join(QLatin1String("\n")); getPythonCommandOutput(autorunScripts); + m_variableModel->update(); } const QString& importerFile = QLatin1String(":py/import_default_modules.py"); evaluateExpression(fromSource(importerFile), Cantor::Expression::DeleteOnFinish, true); - listVariables(); - changeStatus(Session::Done); emit loginDone(); } @@ -123,7 +126,6 @@ m_pProcess->terminate(); m_variableModel->clearVariables(); - emit clearVariables(); qDebug()<<"logout"; changeStatus(Status::Disable); @@ -283,6 +285,7 @@ void PythonSession::updateOutput() { + m_needUpdate |= !m_currentExpression->isInternal(); if(m_error.isEmpty()){ m_currentExpression->parseOutput(m_output); @@ -293,7 +296,11 @@ qDebug() << "error: " << m_error; } - listVariables(); + if (m_needUpdate) + { + m_variableModel->update(); + m_needUpdate = false; + } changeStatus(Cantor::Session::Done); } @@ -307,60 +314,11 @@ updateOutput(); } -void PythonSession::listVariables() -{ - const QString& listVariableCommand = QLatin1String( - "try: \n" - " import numpy \n" - " __cantor_numpy_internal__ = numpy.get_printoptions()['threshold'] \n" - " numpy.set_printoptions(threshold=100000000) \n" - "except ModuleNotFoundError: \n" - " pass \n" - - "print(globals()) \n" - - "try: \n" - " import numpy \n" - " numpy.set_printoptions(threshold=__cantor_numpy_internal__) \n" - " del __cantor_numpy_internal__ \n" - "except ModuleNotFoundError: \n" - " pass \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 '")) /*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, m_pythonVersion); - QObject::connect(this, SIGNAL(updateHighlighter()), highlighter, SLOT(updateHighlight())); - QObject::connect(this, SIGNAL(newVariable(QString)), highlighter, SLOT(addVariable(QString))); - connect(this, &PythonSession::clearVariables, highlighter, &PythonHighlighter::clearVariables); + connect ( m_variableModel, &Cantor::DefaultVariableModel::variablesAdded, highlighter, &PythonHighlighter::addUserVariable); + connect ( m_variableModel, &Cantor::DefaultVariableModel::variablesRemoved, highlighter, &PythonHighlighter::removeUserVariable); return highlighter; } diff --git a/src/backends/python2/testpython2.h b/src/backends/python/pythonvariablemodel.h copy from src/backends/python2/testpython2.h copy to src/backends/python/pythonvariablemodel.h --- a/src/backends/python2/testpython2.h +++ b/src/backends/python/pythonvariablemodel.h @@ -15,26 +15,29 @@ Boston, MA 02110-1301, USA. --- - Copyright (C) 2013 Tuukka Verho - */ + Copyright (C) 2019 Nikita Sirgienko +*/ -#ifndef _TESTPYTHON2_H -#define _TESTPYTHON2_H +#ifndef _PYTHONVARIABLEMODEL_H +#define _PYTHONVARIABLEMODEL_H -#include "backendtest.h" +#include "defaultvariablemodel.h" +class PythonSession; +class QDBusInterface; -class TestPython2 : public BackendTest +class PythonVariableModel : public Cantor::DefaultVariableModel { - Q_OBJECT - private Q_SLOTS: - void testImportNumpy(); - void testCodeWithComments(); - void testSimpleCode(); - void testMultilineCode(); + public: + PythonVariableModel( PythonSession* session); + ~PythonVariableModel() override = default; + + void update() override; + + void setPythonServer(QDBusInterface* pIface); private: - QString backendName() override; + QDBusInterface* m_pIface; }; -#endif /* _TESTPYTHON2_H */ +#endif /* _PYTHONVARIABLEMODEL_H */ diff --git a/src/backends/python/pythonvariablemodel.cpp b/src/backends/python/pythonvariablemodel.cpp new file mode 100644 --- /dev/null +++ b/src/backends/python/pythonvariablemodel.cpp @@ -0,0 +1,62 @@ +/* + 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 "pythonvariablemodel.h" +#include "pythonsession.h" +#include "textresult.h" + +#include +#include +#include +#include + +using namespace Cantor; + +PythonVariableModel::PythonVariableModel(PythonSession* session): + DefaultVariableModel(session), + m_pIface(nullptr) +{ +} + +void PythonVariableModel::setPythonServer(QDBusInterface* pIface) +{ + m_pIface = pIface; +} + +void PythonVariableModel::update() +{ + if (!m_pIface) + return; + + bool variableManagement = static_cast(session())->variableManagement(); + const QString& data = QDBusReply(m_pIface->call(QString::fromLatin1("variables"), variableManagement)).value(); + const QStringList& records = data.split(QChar(30), QString::SkipEmptyParts); + + QList variables; + for (const QString& record : records) + { + const QString& name = record.section(QChar(31), 0, 0); + const QString& value = record.section(QChar(31), 1, 1); + + variables << Variable{name, value}; + } + + setVariables(variables); +} diff --git a/src/backends/python/settings.ui b/src/backends/python/settings.ui --- a/src/backends/python/settings.ui +++ b/src/backends/python/settings.ui @@ -31,6 +31,16 @@ Integrate Plots in Worksheet + + + + + Let Cantor follow the creation/destruction of variables + + + Enable Variable Management + + diff --git a/src/backends/python2/python2backend.h b/src/backends/python2/python2backend.h --- a/src/backends/python2/python2backend.h +++ b/src/backends/python2/python2backend.h @@ -33,6 +33,7 @@ QString id() const override; QString version() const override; + Cantor::Backend::Capabilities capabilities() const override; QUrl helpUrl() const override; QString description() const override; diff --git a/src/backends/python2/python2backend.cpp b/src/backends/python2/python2backend.cpp --- a/src/backends/python2/python2backend.cpp +++ b/src/backends/python2/python2backend.cpp @@ -53,6 +53,21 @@ return QLatin1String("2.7"); } +Cantor::Backend::Capabilities Python2Backend::capabilities() const +{ + qDebug()<<"Requesting capabilities of Python3Session"; + + Backend::Capabilities cap = + Cantor::Backend::SyntaxHighlighting | + Cantor::Backend::Completion | + Cantor::Backend::SyntaxHelp; + + if(PythonSettings::variableManagement()) + cap |= Cantor::Backend::VariableManagement; + + return cap; +} + QUrl Python2Backend::helpUrl() const { const QUrl& localDoc = PythonSettings::self()->localDoc(); diff --git a/src/backends/python2/python2backend.kcfg b/src/backends/python2/python2backend.kcfg --- a/src/backends/python2/python2backend.kcfg +++ b/src/backends/python2/python2backend.kcfg @@ -12,6 +12,10 @@ false + + + true + diff --git a/src/backends/python2/python2session.h b/src/backends/python2/python2session.h --- a/src/backends/python2/python2session.h +++ b/src/backends/python2/python2session.h @@ -30,6 +30,7 @@ bool integratePlots() const override; QStringList autorunScripts() const override; + bool variableManagement() const override; }; #endif diff --git a/src/backends/python2/python2session.cpp b/src/backends/python2/python2session.cpp --- a/src/backends/python2/python2session.cpp +++ b/src/backends/python2/python2session.cpp @@ -44,3 +44,8 @@ { return PythonSettings::autorunScripts(); } + +bool Python2Session::variableManagement() const +{ + return PythonSettings::variableManagement(); +} diff --git a/src/backends/python2/testpython2.h b/src/backends/python2/testpython2.h --- a/src/backends/python2/testpython2.h +++ b/src/backends/python2/testpython2.h @@ -33,6 +33,10 @@ void testSimpleCode(); void testMultilineCode(); + void testVariablesCreatingFromCode(); + void testVariableCleanupAfterRestart(); + void testDictVariable(); + private: QString backendName() override; }; diff --git a/src/backends/python2/testpython2.cpp b/src/backends/python2/testpython2.cpp --- a/src/backends/python2/testpython2.cpp +++ b/src/backends/python2/testpython2.cpp @@ -24,6 +24,7 @@ #include "backend.h" #include "expression.h" #include "result.h" +#include "defaultvariablemodel.h" QString TestPython2::backendName() { @@ -81,7 +82,70 @@ QString result=e->result()->toHtml(); QCOMPARE( cleanOutput(result ), QLatin1String("4 6") ); + + evalExp(QLatin1String("del a; del b")); +} + +void TestPython2::testVariablesCreatingFromCode() +{ + QAbstractItemModel* model = session()->variableModel(); + QVERIFY(model != nullptr); + + Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); + QVERIFY(e!=nullptr); + + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(2, model->rowCount()); + + QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a")); + QCOMPARE(model->index(0,1).data().toString(), QLatin1String("15")); + + QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b")); + QCOMPARE(model->index(1,1).data().toString(), QLatin1String("'S'")); + + evalExp(QLatin1String("del a; del b")); } +void TestPython2::testVariableCleanupAfterRestart() +{ + Cantor::DefaultVariableModel* model = static_cast(session()->variableModel()); + QVERIFY(model != nullptr); + + Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); + QVERIFY(e!=nullptr); + + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(2, static_cast(model)->rowCount()); + + session()->logout(); + session()->login(); + + QCOMPARE(0, static_cast(model)->rowCount()); +} + +void TestPython2::testDictVariable() +{ + Cantor::DefaultVariableModel* model = static_cast(session()->variableModel()); + QVERIFY(model != nullptr); + + Cantor::Expression* e=evalExp(QLatin1String("d = {'value': 33}")); + + QVERIFY(e!=nullptr); + + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(1, static_cast(model)->rowCount()); + QCOMPARE(model->index(0,0).data().toString(), QLatin1String("d")); + QCOMPARE(model->index(0,1).data().toString(), QLatin1String("{'value': 33}")); + + evalExp(QLatin1String("del d")); +} + + QTEST_MAIN(TestPython2) diff --git a/src/backends/python3/python3backend.h b/src/backends/python3/python3backend.h --- a/src/backends/python3/python3backend.h +++ b/src/backends/python3/python3backend.h @@ -32,6 +32,7 @@ QString id() const override; QString version() const override; + Cantor::Backend::Capabilities capabilities() const override; QUrl helpUrl() const override; QString description() const override; diff --git a/src/backends/python3/python3backend.cpp b/src/backends/python3/python3backend.cpp --- a/src/backends/python3/python3backend.cpp +++ b/src/backends/python3/python3backend.cpp @@ -46,6 +46,21 @@ return QLatin1String("3.6"); } +Cantor::Backend::Capabilities Python3Backend::capabilities() const +{ + qDebug()<<"Requesting capabilities of Python3Session"; + + Backend::Capabilities cap = + Cantor::Backend::SyntaxHighlighting | + Cantor::Backend::Completion | + Cantor::Backend::SyntaxHelp; + + if(PythonSettings::variableManagement()) + cap |= Cantor::Backend::VariableManagement; + + return cap; +} + QUrl Python3Backend::helpUrl() const { const QUrl& localDoc = PythonSettings::self()->localDoc(); diff --git a/src/backends/python3/python3backend.kcfg b/src/backends/python3/python3backend.kcfg --- a/src/backends/python3/python3backend.kcfg +++ b/src/backends/python3/python3backend.kcfg @@ -12,6 +12,10 @@ false + + + true + diff --git a/src/backends/python3/python3session.h b/src/backends/python3/python3session.h --- a/src/backends/python3/python3session.h +++ b/src/backends/python3/python3session.h @@ -30,6 +30,7 @@ bool integratePlots() const override; QStringList autorunScripts() const override; + bool variableManagement() const override; }; #endif diff --git a/src/backends/python3/python3session.cpp b/src/backends/python3/python3session.cpp --- a/src/backends/python3/python3session.cpp +++ b/src/backends/python3/python3session.cpp @@ -35,3 +35,9 @@ { return PythonSettings::autorunScripts(); } + +bool Python3Session::variableManagement() const +{ + return PythonSettings::variableManagement(); +} + diff --git a/src/backends/python3/testpython3.h b/src/backends/python3/testpython3.h --- a/src/backends/python3/testpython3.h +++ b/src/backends/python3/testpython3.h @@ -32,6 +32,10 @@ void testPython3Code(); void testSimplePlot(); + void testVariablesCreatingFromCode(); + void testVariableCleanupAfterRestart(); + void testDictVariable(); + private: QString backendName() override; }; diff --git a/src/backends/python3/testpython3.cpp b/src/backends/python3/testpython3.cpp --- a/src/backends/python3/testpython3.cpp +++ b/src/backends/python3/testpython3.cpp @@ -24,6 +24,7 @@ #include "backend.h" #include "expression.h" #include "imageresult.h" +#include "defaultvariablemodel.h" QString TestPython3::backendName() { @@ -112,6 +113,68 @@ QVERIFY(e->results().size() == 1); const Cantor::ImageResult* result = dynamic_cast(e->result()); QVERIFY(result != nullptr); + + evalExp(QLatin1String("del t; del s")); +} + +void TestPython3::testVariablesCreatingFromCode() +{ + QAbstractItemModel* model = session()->variableModel(); + QVERIFY(model != nullptr); + + Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); + QVERIFY(e!=nullptr); + + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(2, model->rowCount()); + + QCOMPARE(model->index(0,0).data().toString(), QLatin1String("a")); + QCOMPARE(model->index(0,1).data().toString(), QLatin1String("15")); + + QCOMPARE(model->index(1,0).data().toString(), QLatin1String("b")); + QCOMPARE(model->index(1,1).data().toString(), QLatin1String("'S'")); + + evalExp(QLatin1String("del a; del b")); +} + +void TestPython3::testVariableCleanupAfterRestart() +{ + Cantor::DefaultVariableModel* model = static_cast(session()->variableModel()); + QVERIFY(model != nullptr); + + Cantor::Expression* e=evalExp(QLatin1String("a = 15; b = 'S';")); + QVERIFY(e!=nullptr); + + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(2, static_cast(model)->rowCount()); + + session()->logout(); + session()->login(); + + QCOMPARE(0, static_cast(model)->rowCount()); +} + +void TestPython3::testDictVariable() +{ + Cantor::DefaultVariableModel* model = static_cast(session()->variableModel()); + QVERIFY(model != nullptr); + + Cantor::Expression* e=evalExp(QLatin1String("d = {'value': 33}")); + + QVERIFY(e!=nullptr); + + if(session()->status()==Cantor::Session::Running) + waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); + + QCOMPARE(1, static_cast(model)->rowCount()); + QCOMPARE(model->index(0,0).data().toString(), QLatin1String("d")); + QCOMPARE(model->index(0,1).data().toString(), QLatin1String("{'value': 33}")); + + evalExp(QLatin1String("del d")); } QTEST_MAIN(TestPython3) diff --git a/src/backends/sage/sagesession.cpp b/src/backends/sage/sagesession.cpp --- a/src/backends/sage/sagesession.cpp +++ b/src/backends/sage/sagesession.cpp @@ -298,12 +298,8 @@ { case Cantor::Expression::Done: case Cantor::Expression::Error: - expressionQueue().removeFirst(); - if (expressionQueue().isEmpty()) - changeStatus(Done); - else - runFirstExpression(); - break; + finishFirstExpression(); + default: break; } diff --git a/src/lib/defaultvariablemodel.h b/src/lib/defaultvariablemodel.h --- a/src/lib/defaultvariablemodel.h +++ b/src/lib/defaultvariablemodel.h @@ -84,6 +84,27 @@ */ Session* session() const; + /** + * Returns variables, stored in this model, as @see Variable. + */ + QList variables() const; + + /** + * Returns names of stored variables + */ + QStringList variableNames() const; + + /** + * Return functions, stored in this model + */ + QStringList functions() const; + + //TODO: improve the description? + /** + * Starts updating variable model (variable lists, etc.). Usually executed after finished all user's commands + */ + virtual void update() {}; + public Q_SLOTS: /** * Adds a variable to the model. @@ -113,6 +134,34 @@ */ void clearVariables(); + /** + * Clears all functions + */ + void clearFunctions(); + + +Q_SIGNALS: + /** + * Emitted after adding new variables + * @param variables list of new variables + */ + void variablesAdded(const QStringList& variables); + + /** + * Emitted after variables removing + * @param variables list of removed variables + */ + void variablesRemoved(const QStringList& variables); + + /** + * Similar to @c variablesAdded + */ + void functionsAdded(const QStringList& names); + + /** + * Similar to @c variablesRemoved + */ + void functionsRemoved(const QStringList funcs); protected: QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -123,6 +172,9 @@ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex& index) const override; + void setVariables(const QList& newVars); + void setFunctions(const QStringList& newFuns); + enum Column { NameColumn = 0, diff --git a/src/lib/defaultvariablemodel.cpp b/src/lib/defaultvariablemodel.cpp --- a/src/lib/defaultvariablemodel.cpp +++ b/src/lib/defaultvariablemodel.cpp @@ -31,6 +31,7 @@ { public: QList variables; + QStringList functions; Session* session; VariableManagementExtension* extension; @@ -146,10 +147,12 @@ 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(); } @@ -166,25 +169,167 @@ 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::clearFunctions() +{ + Q_D(DefaultVariableModel); + QStringList names = d->functions; + d->functions.clear(); + emit functionsRemoved(names); +} + +void DefaultVariableModel::setVariables(const QList& newVars) +{ + Q_D(DefaultVariableModel); + + QStringList addedVars; + QStringList removedVars; + + // Handle deleted vars + 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) + { + 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(); + } + } + + emit variablesAdded(addedVars); + emit variablesRemoved(removedVars); +} + +void DefaultVariableModel::setFunctions(const QStringList& newFuncs) +{ + Q_D(DefaultVariableModel); + QStringList addedFuncs; + QStringList removedFuncs; + + //remove the old variables + int i = 0; + while (i < d->functions.size()) + { + //check if this var is present in the new variables + bool found=false; + for (const QString& func : newFuncs) + if(d->functions[i] == func) + { + found=true; + break; + } + + if(!found) + { + removedFuncs<functions[i]; + d->functions.removeAt(i); + } + else + i++; + } + + for (const QString& func : newFuncs) + { + if (!d->functions.contains(func)) + { + addedFuncs<functions.append(func); + } + } + + emit functionsAdded(addedFuncs); + emit functionsRemoved(removedFuncs); } 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; +} + +QStringList DefaultVariableModel::functions() const +{ + Q_D(const DefaultVariableModel); + return d->functions; +} + bool operator==(const Cantor::DefaultVariableModel::Variable& one, const Cantor::DefaultVariableModel::Variable& other) { return one.name == other.name; diff --git a/src/lib/session.h b/src/lib/session.h --- a/src/lib/session.h +++ b/src/lib/session.h @@ -40,6 +40,7 @@ class SessionPrivate; class CompletionObject; class SyntaxHelpObject; +class DefaultVariableModel; /** * The Session object is the main class used to interact with a Backend. @@ -53,16 +54,22 @@ 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 - Disable ///< the session don't login yet, or already logout + Done, ///< the session has done all the jobs, and is now waiting for more + Disable ///< the session don't login yet, or already logout }; /** * Create a new Session. This should not yet set up the complete session, * thats job of the login() function * @see login() */ explicit Session( Backend* backend); + + /** + * Similar to Session::Session, but also specify variable model for automatically handles model's updates + */ + explicit Session( Backend* backend, DefaultVariableModel* model); + /** * Destructor */ @@ -208,6 +215,25 @@ */ virtual void runFirstExpression(); + /** + * Opposite action to Session::runFirstExpression() + * This method dequeue the expression and go to next expression, if queue not empty + * Also, this method updates variableModel, if needed + * If queue empty, session change status to Done + */ + virtual void finishFirstExpression(); + + /** + * Starts variable update immideatly, usefull for subclasses, which run internal command + * which could change variables listen + */ + virtual void forceVariableUpdate(); + + /** + * Setting variable model, usefull, if model constructor requires functional session + */ + void setVariableModel(DefaultVariableModel* model); + Q_SIGNALS: void statusChanged(Cantor::Session::Status newStatus); void loginStarted(); diff --git a/src/lib/session.cpp b/src/lib/session.cpp --- a/src/lib/session.cpp +++ b/src/lib/session.cpp @@ -22,29 +22,38 @@ using namespace Cantor; #include "backend.h" +#include "defaultvariablemodel.h" #include #include class Cantor::SessionPrivate { public: - SessionPrivate() : backend(nullptr), status(Session::Disable), typesettingEnabled(false), expressionCount(0) + SessionPrivate() : backend(nullptr), status(Session::Disable), typesettingEnabled(false), expressionCount(0), variableModel(nullptr), needUpdate(false) { } Backend* backend; Session::Status status; bool typesettingEnabled; int expressionCount; QList expressionQueue; + DefaultVariableModel* variableModel; + bool needUpdate; }; Session::Session( Backend* backend ) : QObject(backend), d(new SessionPrivate) { d->backend=backend; } +Session::Session( Backend* backend, DefaultVariableModel* model) : QObject(backend), d(new SessionPrivate) +{ + d->backend=backend; + d->variableModel=model; +} + Session::~Session() { delete d; @@ -62,8 +71,8 @@ //run the newly added expression immediately if it's the only one in the queue if (d->expressionQueue.size() == 1) { - changeStatus(Cantor::Session::Running); - runFirstExpression(); + changeStatus(Cantor::Session::Running); + runFirstExpression(); } else expr->setStatus(Cantor::Expression::Queued); @@ -74,6 +83,23 @@ } +void Session::finishFirstExpression() +{ + if (!d->expressionQueue.isEmpty()) + d->needUpdate |= !d->expressionQueue.takeFirst()->isInternal(); + + if (d->expressionQueue.isEmpty()) + if (d->variableModel && d->needUpdate) + { + d->variableModel->update(); + d->needUpdate = false; + } + else + changeStatus(Done); + else + runFirstExpression(); +} + Backend* Session::backend() { return d->backend; @@ -135,10 +161,25 @@ QAbstractItemModel* Session::variableModel() { - //Return 0 per default, so Backends not offering variable management don't + //Return deafult session model per default + //By default, variableModel is nullptr, so Backends not offering variable management don't //have to reimplement this. This method should only be called on backends with //VariableManagement Capability flag - return nullptr; + return d->variableModel; +} + +void Session::forceVariableUpdate() +{ + if (d->variableModel) + { + d->variableModel->update(); + d->needUpdate = false; + } +} + +void Cantor::Session::setVariableModel(Cantor::DefaultVariableModel* model) +{ + d->variableModel = model; } int Session::nextExpressionId()