diff --git a/src/backends/R/CMakeLists.txt b/src/backends/R/CMakeLists.txt index ff77af16..5930d740 100644 --- a/src/backends/R/CMakeLists.txt +++ b/src/backends/R/CMakeLists.txt @@ -1,29 +1,41 @@ include_directories(${R_INCLUDEDIR}) LINK_DIRECTORIES(${R_SHAREDLIBDIR}) add_subdirectory(rserver) set( RBackend_SRCS rbackend.cpp rsession.cpp rexpression.cpp rextensions.cpp rcompletionobject.cpp rhighlighter.cpp rkeywords.cpp rsettingswidget.cpp + rvariablemodel.cpp ) kconfig_add_kcfg_files(RBackend_SRCS rserver/settings.kcfgc) set(network_xml rserver/org.kde.Cantor.R.xml) QT5_ADD_DBUS_INTERFACE(RBackend_SRCS ${network_xml} rserver_interface ) ki18n_wrap_ui(RBackend_SRCS settings.ui) add_backend(rbackend ${RBackend_SRCS}) 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 index ab619b9b..efeae8ef 100644 --- a/src/backends/R/rbackend.cpp +++ b/src/backends/R/rbackend.cpp @@ -1,111 +1,116 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "rbackend.h" #include "rsession.h" #include "rextensions.h" #include "settings.h" #include "rsettingswidget.h" #include #include "cantor_macros.h" #include RBackend::RBackend(QObject* parent,const QList& args) : Cantor::Backend(parent, args) { setObjectName(QLatin1String("rbackend")); qDebug()<<"Creating RBackend"; new RScriptExtension(this); new RPlotExtension(this); new RVariableManagementExtension(this); } RBackend::~RBackend() { qDebug()<<"Destroying RBackend"; } QString RBackend::id() const { return QLatin1String("r"); } QString RBackend::version() const { return QLatin1String("Undefined"); } Cantor::Session* RBackend::createSession() { qDebug()<<"Spawning a new R session"; return new RSession(this); } 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 { QFileInfo info(QStandardPaths::findExecutable( QLatin1String("cantor_rserver") ) ); return info.isExecutable(); } QWidget* RBackend::settingsWidget(QWidget* parent) const { return new RSettingsWidget(parent); } KConfigSkeleton* RBackend::config() const { return RServerSettings::self(); } QUrl RBackend::helpUrl() const { const QUrl& localDoc = RServerSettings::self()->localDoc(); if (!localDoc.isEmpty()) return localDoc; else return QUrl(i18nc("the url to the documentation of R, please check if there is a translated version and use the correct url", "http://rwiki.sciviews.org/doku.php?id=rdoc:rdoc")); } QString RBackend::description() const { return i18n("R is a language and environment for statistical computing and graphics, similar to the S language and environment.
"\ "It provides a wide variety of statistical (linear and nonlinear modelling, "\ "classical statistical tests, time-series analysis, classification, clustering, ...) "\ "and graphical techniques, and is highly extensible. The S language is often the "\ "vehicle of choice for research in statistical methodology, "\ "and R provides an Open Source route to participation in that activity."); } K_PLUGIN_FACTORY_WITH_JSON(rbackend, "rbackend.json", registerPlugin();) #include "rbackend.moc" diff --git a/src/backends/R/rhighlighter.cpp b/src/backends/R/rhighlighter.cpp index 9ae67936..cdbce105 100644 --- a/src/backends/R/rhighlighter.cpp +++ b/src/backends/R/rhighlighter.cpp @@ -1,92 +1,83 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2010 Oleksiy Protas */ #include "rhighlighter.h" #include "rkeywords.h" +#include "rsession.h" #include #include const QStringList RHighlighter::operators_list=QStringList() << QLatin1String("(\\+|\\-|\*{1,2}|/|<=?|>=?|={1,2}|\\!=?|\|{1,2}|&{1,2}|:{1,3}|\^|@|\\$|~)") << QLatin1String("%[^%]*%"); // Taken in r.xml syntax file from KSyntaxHighlighter const QStringList RHighlighter::specials_list=QStringList() << QLatin1String("BUG") << QLatin1String("TODO") << QLatin1String("FIXME") << QLatin1String("NB") << QLatin1String("WARNING") << QLatin1String("ERROR"); -RHighlighter::RHighlighter(QObject* parent) : Cantor::DefaultHighlighter(parent) +RHighlighter::RHighlighter(QObject* parent, RSession* session) : Cantor::DefaultHighlighter(parent, session) { addKeywords(RKeywords::instance()->keywords()); foreach (const QString& s, operators_list) operators.append(QRegExp(s)); foreach (const QString& s, specials_list) 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) { int index = p.indexIn(text); while (index >= 0) { int length = p.matchedLength(); setFormat(index+(shift?1:0), length-(shift?1:0), fmt); index = p.indexIn(text, index + length); } } void RHighlighter::massFormat(const QVector &p, const QTextCharFormat &fmt, const QString& text,bool shift) { foreach (const QRegExp &rule, p) formatRule(rule,fmt,text,shift); } void RHighlighter::highlightBlock(const QString& text) { if(text.isEmpty()) return; //Do some backend independent highlighting (brackets etc.) DefaultHighlighter::highlightBlock(text); //Let's mark every functionlike call as an error, then paint right ones in their respective format // TODO: find more elegant solution not involving double formatting formatRule(QRegExp(QLatin1String("\\b[A-Za-z0-9_]+(?=\\()")),errorFormat(),text); //formatRule(QRegExp("[^A-Za-z_]-?([0-9]+)?(((e|i)?-?)|\\.)[0-9]*L?"),numberFormat(),text,true); // TODO: erroneous number formats, refine massFormat(operators,operatorFormat(),text); massFormat(specials,commentFormat(),text); // FIXME must be distinct massFormat(functions,functionFormat(),text); massFormat(variables,variableFormat(),text); formatRule(QRegExp(QLatin1String("\"[^\"]+\"")),stringFormat(),text); // WARNING a bit redundant } - -void RHighlighter::updateHighlighting() -{ - emit rulesChanged(); -} diff --git a/src/backends/R/rhighlighter.h b/src/backends/R/rhighlighter.h index a08a6548..7d101ec5 100644 --- a/src/backends/R/rhighlighter.h +++ b/src/backends/R/rhighlighter.h @@ -1,56 +1,51 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2010 Oleksiy Protas */ #ifndef _RHIGHLIGHTER_H #define _RHIGHLIGHTER_H #include "defaulthighlighter.h" +class RSession; + class RHighlighter : public Cantor::DefaultHighlighter { Q_OBJECT public: - explicit RHighlighter( QObject* parent); + explicit RHighlighter( QObject* parent, RSession* session); ~RHighlighter() override = default; protected: void highlightBlock(const QString &text) override; - public Q_SLOTS: - void refreshSyntaxRegExps(); - void updateHighlighting(); - - Q_SIGNALS: - void syntaxRegExps(QVector&,QVector&); - 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); static const QStringList operators_list; static const QStringList specials_list; QVector operators; QVector specials; QVector functions; QVector variables; }; #endif /* _RHIGHLIGHTER_H */ diff --git a/src/backends/R/rserver/rserver.cpp b/src/backends/R/rserver/rserver.cpp index 8c18c076..0cdb3789 100644 --- a/src/backends/R/rserver/rserver.cpp +++ b/src/backends/R/rserver/rserver.cpp @@ -1,518 +1,513 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2010 Oleksiy Protas */ // TODO: setStatus in syntax and completions, to be or not to be? // on the one hand comme il faut, on another, causes flickering in UI #include "rserver.h" #include #include "radaptor.h" #include "rcallbacks.h" #include "settings.h" #include #include #include #include #include //R includes #include #include #include #include #define R_INTERFACE_PTRS #include RServer::RServer() : m_isInitialized(false),m_isCompletionAvailable(false) { new RAdaptor(this); m_tmpDir = QDir::tempPath() + QString::fromLatin1("/cantor_rserver-%1").arg(getpid()); QDir dir; dir.mkdir(m_tmpDir); qDebug()<<"storing plots at "<integratePlots()) { qDebug()<<"integrating plots"; newPlotDevice(); } //Loading automatic run scripts foreach (const QString& path, RServerSettings::self()->autorunScripts()) { int errorOccurred=0; if (QFile::exists(path)) R_tryEval(lang2(install("source"),mkString(path.toUtf8().data())),nullptr,&errorOccurred); // TODO: error handling else { qDebug()<<(QLatin1String("Script ")+path+QLatin1String(" not found")); // FIXME: or should we throw a messagebox } } qDebug()<<"done initializing"; - - // FIXME: other way to search symbols, see listSymbols for details - listSymbols(); } //Code from the RInside library void RServer::autoload() { #include "rautoloads.h" /* Autoload default packages and names from autoloads.h * * This function behaves in almost every way like * R's autoload: * function (name, package, reset = FALSE, ...) * { * if (!reset && exists(name, envir = .GlobalEnv, inherits = FALSE)) * stop("an object with that name already exists") * m <- match.call() * m[[1]] <- as.name("list") * newcall <- eval(m, parent.frame()) * newcall <- as.call(c(as.name("autoloader"), newcall)) * newcall$reset <- NULL * if (is.na(match(package, .Autoloaded))) * assign(".Autoloaded", c(package, .Autoloaded), env = .AutoloadEnv) * do.call("delayedAssign", list(name, newcall, .GlobalEnv, * .AutoloadEnv)) * invisible() * } * * What's missing is the updating of the string vector .Autoloaded with * the list of packages, which by my code analysis is useless and only * for informational purposes. * */ //void autoloads(void){ SEXP da, dacall, al, alcall, AutoloadEnv, name, package; int i,j, idx=0, errorOccurred, ptct; /* delayedAssign call*/ PROTECT(da = Rf_findFun(Rf_install("delayedAssign"), R_GlobalEnv)); PROTECT(AutoloadEnv = Rf_findVar(Rf_install(".AutoloadEnv"), R_GlobalEnv)); if (AutoloadEnv == R_NilValue){ qDebug()<<"Cannot find .AutoloadEnv"; //exit(1); } PROTECT(dacall = allocVector(LANGSXP,5)); SETCAR(dacall,da); /* SETCAR(CDR(dacall),name); */ /* arg1: assigned in loop */ /* SETCAR(CDR(CDR(dacall)),alcall); */ /* arg2: assigned in loop */ SETCAR(CDR(CDR(CDR(dacall))),R_GlobalEnv); /* arg3 */ SETCAR(CDR(CDR(CDR(CDR(dacall)))),AutoloadEnv); /* arg3 */ /* autoloader call */ PROTECT(al = Rf_findFun(Rf_install("autoloader"), R_GlobalEnv)); PROTECT(alcall = allocVector(LANGSXP,3)); SET_TAG(alcall, R_NilValue); /* just like do_ascall() does */ SETCAR(alcall,al); /* SETCAR(CDR(alcall),name); */ /* arg1: assigned in loop */ /* SETCAR(CDR(CDR(alcall)),package); */ /* arg2: assigned in loop */ ptct = 5; for(i = 0; i < packc; ++i){ idx += (i != 0)? packobjc[i-1] : 0; for (j = 0; j < packobjc[i]; ++j){ /*printf("autload(%s,%s)\n",packobj[idx+j],pack[i]);*/ PROTECT(name = NEW_CHARACTER(1)); PROTECT(package = NEW_CHARACTER(1)); SET_STRING_ELT(name, 0, COPY_TO_USER_STRING(packobj[idx+j])); SET_STRING_ELT(package, 0, COPY_TO_USER_STRING(pack[i])); /* Set up autoloader call */ PROTECT(alcall = allocVector(LANGSXP,3)); SET_TAG(alcall, R_NilValue); /* just like do_ascall() does */ SETCAR(alcall,al); SETCAR(CDR(alcall),name); SETCAR(CDR(CDR(alcall)),package); /* Setup delayedAssign call */ SETCAR(CDR(dacall),name); SETCAR(CDR(CDR(dacall)),alcall); R_tryEval(dacall,R_GlobalEnv,&errorOccurred); if (errorOccurred){ qDebug()<<"Error calling delayedAssign!"; //exit(1); } ptct += 3; } } UNPROTECT(ptct); /* Initialize the completion libraries if needed, adapted from sys-std.c of R */ // TODO: should we do this or init on demand? // if (completion is needed) // TODO: discuss how to pass parameter { /* First check if namespace is loaded */ if (findVarInFrame(R_NamespaceRegistry,install("utils"))==R_UnboundValue) { /* Then try to load it */ SEXP cmdSexp, cmdexpr; ParseStatus status; int i; const char *p="try(loadNamespace('rcompgen'), silent=TRUE)"; PROTECT(cmdSexp=mkString(p)); cmdexpr=PROTECT(R_ParseVector(cmdSexp,-1,&status,R_NilValue)); if(status==PARSE_OK) { for(i=0;icmd=cmd; expr->hasOtherResults=false; setStatus(RServer::Busy); setCurrentExpression(expr); expr->std_buffer.clear(); expr->err_buffer.clear(); ReturnCode returnCode=RServer::SuccessCode; QString returnText; QStringList neededFiles; //Code to evaluate an R function (taken from RInside library) ParseStatus status; SEXP cmdSexp, cmdexpr = R_NilValue; SEXP result; int i, errorOccurred; QByteArray memBuf; memBuf.append(cmd.toUtf8()); PROTECT(cmdSexp = allocVector(STRSXP, 1)); SET_STRING_ELT(cmdSexp, 0, mkChar((char*)memBuf.data())); cmdexpr = PROTECT(R_ParseVector(cmdSexp, -1, &status, R_NilValue)); switch (status) { case PARSE_OK: qDebug()<<"PARSING "< 1 */ for (i = 0; i < length(cmdexpr); ++i) { result = R_tryEval(VECTOR_ELT(cmdexpr, i), nullptr, &errorOccurred); if (errorOccurred) { qDebug()<<"Error occurred."; break; } // TODO: multiple results } memBuf.clear(); break; case PARSE_INCOMPLETE: /* need to read another line */ qDebug()<<"parse incomplete.."; break; case PARSE_NULL: qDebug()<<"ParseStatus is null: "<std_buffer<<" err: "<err_buffer; //if the command didn't print anything on its own, print the result //TODO: handle some known result types like lists, matrices separately // to make the output look better, by using html (tables etc.) if(expr->std_buffer.isEmpty()&&expr->err_buffer.isEmpty()) { qDebug()<<"printing result..."; SEXP count=PROTECT(R_tryEval(lang2(install("length"),result),nullptr,&errorOccurred)); // TODO: error checks if (*INTEGER(count)==0) qDebug() << "no result, so show nothing"; else Rf_PrintValue(result); UNPROTECT(1); } setCurrentExpression(nullptr); //is this save? if(!expr->err_buffer.isEmpty()) { returnCode=RServer::ErrorCode; returnText=expr->err_buffer; } else { returnCode=RServer::SuccessCode; returnText=expr->std_buffer; } }else { returnCode=RServer::ErrorCode; returnText=i18n("Error Parsing Command"); } if(internal) { qDebug()<<"internal result: "<hasOtherResults=true; newPlotDevice(); neededFiles<hasOtherResults) emit expressionFinished(returnCode, returnText); else showFiles(neededFiles); setStatus(Idle); - - // FIXME: Calling this every evaluation is probably ugly - listSymbols(); } void RServer::completeCommand(const QString& cmd) { // setStatus(RServer::Busy); // TODO: is static okay? guess RServer is a singletone, but ... // TODO: error handling? // TODO: investigate encoding problem // TODO: propage the flexibility of token selection upward // TODO: what if install() fails? investigate // TODO: investigate why errors break the whole foodchain of RServer callbacks in here static SEXP comp_env=R_FindNamespace(mkString("utils")); static SEXP tokenizer_func=install(".guessTokenFromLine"); static SEXP linebuffer_func=install(".assignLinebuffer"); static SEXP buffer_end_func=install(".assignEnd"); static SEXP complete_func=install(".completeToken"); static SEXP retrieve_func=install(".retrieveCompletions"); /* Setting buffer parameters */ int errorOccurred=0; // TODO: error cheks, too lazy to do it now R_tryEval(lang2(linebuffer_func,mkString(cmd.toUtf8().data())),comp_env,&errorOccurred); R_tryEval(lang2(buffer_end_func,ScalarInteger(cmd.size())),comp_env,&errorOccurred); /* Passing the tokenizing work to professionals */ SEXP token=PROTECT(R_tryEval(lang1(tokenizer_func),comp_env,&errorOccurred)); /* Doing the actual stuff */ R_tryEval(lang1(complete_func),comp_env,&errorOccurred); SEXP completions=PROTECT(R_tryEval(lang1(retrieve_func),comp_env,&errorOccurred)); /* Populating the list of completions */ QStringList completionOptions; for (int i=0;i true + + + true + diff --git a/src/backends/R/rsession.cpp b/src/backends/R/rsession.cpp index eb723f5f..a713a55a 100644 --- a/src/backends/R/rsession.cpp +++ b/src/backends/R/rsession.cpp @@ -1,215 +1,184 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2018 Alexander Semke */ #include "rsession.h" #include "rexpression.h" #include "rcompletionobject.h" #include "rhighlighter.h" +#include "rvariablemodel.h" #include #include #include #include #ifndef Q_OS_WIN #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() { if (m_process) m_process->terminate(); } void RSession::login() { qDebug()<<"login"; emit loginStarted(); if(m_process) m_process->deleteLater(); 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(); qDebug()<<"login done"; } void RSession::logout() { qDebug()<<"logout"; m_process->terminate(); - m_variableModel->clearVariables(); - m_variables.clear(); - m_functions.clear(); + variableModel()->clearVariables(); + variableModel()->clearFunctions(); emit symbolsChanged(); changeStatus(Status::Disable); } void RSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } Cantor::Expression* RSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug()<<"evaluating: "<setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } Cantor::CompletionObject* RSession::completionFor(const QString& command, int index) { RCompletionObject *cmp=new RCompletionObject(command, index, this); connect(m_rServer,SIGNAL(completionFinished(QString,QStringList)),cmp,SLOT(receiveCompletions(QString,QStringList))); connect(cmp,SIGNAL(requestCompletion(QString)),m_rServer,SLOT(completeCommand(QString))); return cmp; } 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())); - 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(); + return new RHighlighter(parent, this); } void RSession::serverChangedStatus(int status) { qDebug()<<"changed status to "<(expressionQueue().takeFirst()); + Cantor::Expression* expr = expressionQueue().first(); qDebug()<<"done running "<command(); } - - if(expressionQueue().isEmpty()) - changeStatus(Cantor::Session::Done); - else - runFirstExpression(); + finishFirstExpression(); } else changeStatus(Cantor::Session::Running); } void RSession::runFirstExpression() { if (expressionQueue().isEmpty()) return; disconnect(m_rServer, SIGNAL(expressionFinished(int,QString)), nullptr, nullptr); disconnect(m_rServer, SIGNAL(inputRequested(QString)), nullptr, nullptr); disconnect(m_rServer, SIGNAL(showFilesNeeded(QStringList)), nullptr, nullptr); qDebug()<<"size: "<(expressionQueue().first()); qDebug()<<"running expression: "<command(); connect(m_rServer, SIGNAL(expressionFinished(int,QString)), expr, SLOT(finished(int,QString))); connect(m_rServer, SIGNAL(inputRequested(QString)), expr, SIGNAL(needsAdditionalInformation(QString))); connect(m_rServer, SIGNAL(showFilesNeeded(QStringList)), expr, SLOT(showFilesAsResult(QStringList))); expr->setStatus(Cantor::Expression::Computing); m_rServer->runCommand(expr->command()); } void RSession::sendInputToServer(const QString& input) { QString s=input; if(!input.endsWith(QLatin1Char('\n'))) s+=QLatin1Char('\n'); 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/R/rsession.h b/src/backends/R/rsession.h index 1fdc51d4..b8e46593 100644 --- a/src/backends/R/rsession.h +++ b/src/backends/R/rsession.h @@ -1,75 +1,68 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _RSESSION_H #define _RSESSION_H #include #include #include "session.h" #include "rserver_interface.h" class RExpression; +class RVariableModel; class QProcess; namespace Cantor { class DefaultVariableModel; } class RSession : public Cantor::Session { Q_OBJECT public: explicit RSession( Cantor::Backend* backend); ~RSession() override; void login() override; void logout() override; void interrupt() override; 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/rvariablemodel.cpp b/src/backends/R/rvariablemodel.cpp new file mode 100644 index 00000000..ce9fed44 --- /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/python2/testpython2.h b/src/backends/R/rvariablemodel.h similarity index 59% copy from src/backends/python2/testpython2.h copy to src/backends/R/rvariablemodel.h index ed068c88..ca5b85e7 100644 --- a/src/backends/python2/testpython2.h +++ b/src/backends/R/rvariablemodel.h @@ -1,40 +1,44 @@ /* 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) 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/settings.ui b/src/backends/R/settings.ui index 769643c1..b9316bae 100644 --- a/src/backends/R/settings.ui +++ b/src/backends/R/settings.ui @@ -1,51 +1,61 @@ RSettingsBase 0 0 414 231 Path to local documentation: Integrate Plots in Worksheet + + + + Let Cantor follow the creation/destruction of variables + + + Enable Variable Management + + + Scripts to autorun diff --git a/src/backends/R/testr.cpp b/src/backends/R/testr.cpp new file mode 100644 index 00000000..6c034c67 --- /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 = 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/python2/testpython2.h b/src/backends/R/testr.h similarity index 72% copy from src/backends/python2/testpython2.h copy to src/backends/R/testr.h index ed068c88..9c09e175 100644 --- a/src/backends/python2/testpython2.h +++ b/src/backends/R/testr.h @@ -1,40 +1,39 @@ /* 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) 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/julia/CMakeLists.txt b/src/backends/julia/CMakeLists.txt index ebc7f20d..96c8caf2 100644 --- a/src/backends/julia/CMakeLists.txt +++ b/src/backends/julia/CMakeLists.txt @@ -1,41 +1,42 @@ include_directories(${JULIA_INCLUDE_DIRS}) add_subdirectory(juliaserver) set(JuliaBackend_SRCS juliabackend.cpp juliasession.cpp juliaexpression.cpp juliakeywords.cpp + juliavariablemodel.cpp juliahighlighter.cpp juliaextensions.cpp juliacompletionobject.cpp ) kconfig_add_kcfg_files(JuliaBackend_SRCS settings.kcfgc) ki18n_wrap_ui(JuliaBackend_SRCS settings.ui) add_backend(juliabackend ${JuliaBackend_SRCS}) target_link_libraries(cantor_juliabackend ${JULIA_LIBRARY} Qt5::DBus KF5::SyntaxHighlighting ) 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) install(FILES juliabackend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) file(GLOB scripts "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.jl") install( FILES ${scripts} DESTINATION ${KDE_INSTALL_DATADIR}/cantor/juliabackend/scripts ) diff --git a/src/backends/julia/juliabackend.cpp b/src/backends/julia/juliabackend.cpp index 15510606..bd46f71d 100644 --- a/src/backends/julia/juliabackend.cpp +++ b/src/backends/julia/juliabackend.cpp @@ -1,109 +1,114 @@ /* 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) 2016 Ivan Lakhtanov */ #include "juliabackend.h" #include #include "juliasession.h" #include "ui_settings.h" #include "settings.h" #include "juliaextensions.h" JuliaBackend::JuliaBackend(QObject *parent, const QList &args) : Cantor::Backend(parent, args) { setEnabled(true); new JuliaVariableManagementExtension(this); new JuliaPackagingExtension(this); new JuliaPlotExtension(this); new JuliaScriptExtension(this); new JuliaLinearAlgebraExtension(this); } QString JuliaBackend::id() const { return QLatin1String("julia"); } QString JuliaBackend::version() const { return QLatin1String("1.0.0"); } Cantor::Session *JuliaBackend::createSession() { return new JuliaSession(this); } 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 { return i18n( "

Julia is a high-level, high-performance dynamic programming " "language for technical computing, with syntax that is familiar to " "users of other technical computing environments. It provides a " "sophisticated compiler, distributed parallel execution, numerical " "accuracy, and an extensive mathematical function library.

" ); } QUrl JuliaBackend::helpUrl() const { return QUrl(i18nc( "The url to the documentation of Julia, please check if there is a" " translated version and use the correct url", "http://docs.julialang.org/en/latest/" )); } bool JuliaBackend::requirementsFullfilled() const { return QFileInfo( JuliaSettings::self()->replPath().toLocalFile() ).isExecutable(); } QWidget *JuliaBackend::settingsWidget(QWidget *parent) const { QWidget *widget = new QWidget(parent); Ui::JuliaSettingsBase s; s.setupUi(widget); return widget; } KConfigSkeleton *JuliaBackend::config() const { return JuliaSettings::self(); } K_PLUGIN_FACTORY_WITH_JSON( juliabackend, "juliabackend.json", registerPlugin(); ) #include "juliabackend.moc" diff --git a/src/backends/julia/juliabackend.kcfg b/src/backends/julia/juliabackend.kcfg index fd281a33..11cfe1d1 100644 --- a/src/backends/julia/juliabackend.kcfg +++ b/src/backends/julia/juliabackend.kcfg @@ -1,28 +1,32 @@ QStandardPaths QUrl::fromLocalFile(QStandardPaths::findExecutable(QLatin1String("julia"))) + + + true + true svg diff --git a/src/backends/julia/juliahighlighter.cpp b/src/backends/julia/juliahighlighter.cpp index 8544c7be..cb30988d 100644 --- a/src/backends/julia/juliahighlighter.cpp +++ b/src/backends/julia/juliahighlighter.cpp @@ -1,185 +1,170 @@ /* * 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) 2016 Ivan Lakhtanov */ #include "juliahighlighter.h" #include "juliakeywords.h" +#include "juliasession.h" #include #include #include -JuliaHighlighter::JuliaHighlighter(QObject *parent) - : Cantor::DefaultHighlighter(parent) +JuliaHighlighter::JuliaHighlighter(QObject *parent, JuliaSession* session) + : Cantor::DefaultHighlighter(parent, session) { addKeywords(JuliaKeywords::instance()->keywords()); addVariables(JuliaKeywords::instance()->variables()); addFunctions(JuliaKeywords::instance()->functions()); addFunctions(JuliaKeywords::instance()->plotShowingCommands()); } void JuliaHighlighter::highlightBlock(const QString &text) { if (skipHighlighting(text)) { return; } // Do some backend independent highlighting (brackets etc.) DefaultHighlighter::highlightBlock(text); // Now we are about to make correct strings and comments highlighting // // Main idea: as soon as string starts comment or anything else cant start // until current string ends. The same with comment, except '#' comment // that ends by newline // // To pass information to next block, we are using next states const int IN_MULTILINE_COMMENT = 1; const int IN_CHARACTER = 2; const int IN_SINGLE_QUOTE_STRING = 4; const int IN_TRIPLE_QUOTE_STRING = 8; // Markers of scopes start, ends QRegExp multiLineCommentStart(QLatin1String("#=")); QRegExp multiLineCommentEnd(QLatin1String("=#")); QRegExp characterStartEnd(QLatin1String("'")); QRegExp singleQuoteStringStartEnd(QLatin1String("\"")); QRegExp tripleQuoteStringStartEnd(QLatin1String("\"\"\"")); QRegExp singleLineCommentStart(QLatin1String("#(?!=)")); // Get current state int state = previousBlockState(); if (state == -1) { state = 0; } // This 4 arrays establish matching between state, start marker, end marker // and format to apply QList flags = { IN_TRIPLE_QUOTE_STRING, IN_SINGLE_QUOTE_STRING, IN_CHARACTER, IN_MULTILINE_COMMENT }; QList regexps_starts = { tripleQuoteStringStartEnd, singleQuoteStringStartEnd, characterStartEnd, multiLineCommentStart }; QList regexps_ends = { tripleQuoteStringStartEnd, singleQuoteStringStartEnd, characterStartEnd, multiLineCommentEnd }; QList formats = { stringFormat(), stringFormat(), stringFormat(), commentFormat() }; int pos = 0; // current position in block while (pos < text.length()) { // Trying to close current environments bool triggered = false; for (int i = 0; i < flags.size() && !triggered; i++) { int flag = flags[i]; QRegExp ®exp = regexps_ends[i]; QTextCharFormat &format = formats[i]; if (state & flag) { // Found current state // find where end marker is int new_pos = regexp.indexIn(text, pos); int length; if (new_pos == -1) { // not in this block, highlight till the end length = text.length() - pos; } else { // highlight untill the marker and modify state length = new_pos - pos + regexp.matchedLength(); state -= flag; } // Apply format to the found area setFormat(pos, length, format); pos = pos + length; triggered = true; } } if (triggered) { // We have done something move to next iteration continue; } // Now we should found the scope that start the closest to current // position QRegExp *minRegexp = nullptr; // closest marker int minPos = INT_MAX; // closest pos int minIdx = -1; // closest scope index for (int i = 0; i < regexps_starts.size(); i++) { QRegExp ®exp = regexps_starts[i]; int newPos = regexp.indexIn(text, pos); if (newPos != -1) { minPos = qMin(minPos, newPos); minRegexp = ®exp; minIdx = i; } } // Check where single line comment starts singleLineCommentStart.indexIn(text, pos); int singleLineCommentStartPos = singleLineCommentStart.pos(); if (singleLineCommentStartPos != -1 && singleLineCommentStartPos < minPos) { // single line comment starts earlier setFormat(singleLineCommentStartPos, text.length() - singleLineCommentStartPos, commentFormat()); break; } else if (minRegexp) { // We are going to another scope state += flags[minIdx]; pos = minPos + minRegexp->matchedLength(); setFormat(minPos, minRegexp->matchedLength(), formats[minIdx]); } else { // There is nothing to highlight break; } } setCurrentBlockState(state); } -void JuliaHighlighter::updateHighlight() -{ - // 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); - } - - // Add actual variables and function - addVariables(JuliaKeywords::instance()->variables()); - addFunctions(JuliaKeywords::instance()->functions()); - rehighlight(); -} - QString JuliaHighlighter::nonSeparatingCharacters() const { return QLatin1String("[\\w¡-ﻼ!]"); } diff --git a/src/backends/julia/juliahighlighter.h b/src/backends/julia/juliahighlighter.h index a515906e..4239a4bb 100644 --- a/src/backends/julia/juliahighlighter.h +++ b/src/backends/julia/juliahighlighter.h @@ -1,60 +1,56 @@ /* * 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) 2016 Ivan Lakhtanov */ #pragma once #include "defaulthighlighter.h" +class JuliaSession; + /** * Implementation of JuliaHighlighter * * Takes into account loaded symbols from scope and predefined keywords. * There is no common regexps that bound to fail with such syntax-overloaded * languages as Julia */ class JuliaHighlighter: public Cantor::DefaultHighlighter { Q_OBJECT public: /** * Constructs JuliaHighlighter * * @param parent QObject parent */ - explicit JuliaHighlighter(QObject *parent); + explicit JuliaHighlighter(QObject *parent, JuliaSession* session); ~JuliaHighlighter() override = default; -public Q_SLOTS: - /** - * Call this to update highlighter to the current state of keywords storage - */ - void updateHighlight(); - protected: /** * @see Cantor::DefaultHighlighter::highlightBlock */ void highlightBlock(const QString &text) override; /** * @see Cantor::DefaultHighlighter::nonSeparatingCharacters */ QString nonSeparatingCharacters() const override; }; diff --git a/src/backends/julia/juliaserver/juliaserver.cpp b/src/backends/julia/juliaserver/juliaserver.cpp index 15d734a7..d61bd17d 100644 --- a/src/backends/julia/juliaserver/juliaserver.cpp +++ b/src/backends/julia/juliaserver/juliaserver.cpp @@ -1,249 +1,257 @@ /* * 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) 2016 Ivan Lakhtanov */ #include "juliaserver.h" #include #include #include #include #include #include #include QStringList JuliaServer::INTERNAL_VARIABLES = QStringList() << QLatin1String("__originalSTDOUT__") << QLatin1String("__originalSTDERR__"); JuliaServer::JuliaServer(QObject *parent) : QObject(parent), m_was_exception(false) { } JuliaServer::~JuliaServer() { jl_atexit_hook(0); } void JuliaServer::login(const QString &path) const { #if QT_VERSION_CHECK(JULIA_VERSION_MAJOR, JULIA_VERSION_MINOR, 0) >= QT_VERSION_CHECK(0, 6, 0) Q_UNUSED(path) jl_init(); #else QString dir_path = QFileInfo(path).dir().absolutePath(); jl_init(dir_path.toLatin1().constData()); #endif jl_eval_string("import REPL;"); } #if QT_VERSION_CHECK(JULIA_VERSION_MAJOR, JULIA_VERSION_MINOR, 0) >= QT_VERSION_CHECK(0, 7, 0) #define JULIA_STDOUT "stdout" #define JULIA_STDERR "stderr" #else #define JULIA_STDOUT "STDOUT" #define JULIA_STDERR "STDOUT" #endif void JuliaServer::runJuliaCommand(const QString &command) { // Redirect stdout, stderr to temporary files QTemporaryFile output, error; if (!output.open() || !error.open()) { qFatal("Unable to create temporary files for stdout/stderr"); return; } jl_eval_string("const __originalSTDOUT__ = " JULIA_STDOUT); jl_eval_string("const __originalSTDERR__ = " JULIA_STDERR); jl_eval_string( QString::fromLatin1("redirect_stdout(open(\"%1\", \"w\"))") .arg(output.fileName()).toLatin1().constData() ); jl_eval_string( QString::fromLatin1("redirect_stderr(open(\"%1\", \"w\"))") .arg(error.fileName()).toLatin1().constData() ); jl_module_t* jl_repl_module = (jl_module_t*)(jl_eval_string("REPL")); jl_function_t* jl_ends_func = jl_get_function(jl_repl_module, "ends_with_semicolon"); bool isEndsWithSemicolon = jl_unbox_bool(jl_call1(jl_ends_func, jl_cstr_to_string(command.toStdString().c_str()))); // Run command jl_value_t *val = static_cast( jl_eval_string(command.toUtf8().constData()) ); if (jl_exception_occurred()) { // If exception occurred // Show it to user in stderr #if QT_VERSION_CHECK(JULIA_VERSION_MAJOR, JULIA_VERSION_MINOR, 0) >= QT_VERSION_CHECK(1, 1, 0) jl_value_t *ex = jl_get_ptls_states()->previous_exception; #else jl_value_t *ex = jl_exception_in_transit; #endif jl_printf(JL_STDERR, "error during run:\n"); jl_function_t *showerror = jl_get_function(jl_base_module, "showerror"); jl_value_t *bt = static_cast( jl_eval_string("catch_backtrace()") ); jl_value_t *err_stream = static_cast( jl_eval_string(JULIA_STDERR) ); jl_call3(showerror, err_stream, ex, bt); jl_exception_clear(); m_was_exception = true; } else if (val && !isEndsWithSemicolon) { // no exception occurred // If last result is not nothing, show it jl_function_t *equality = jl_get_function(jl_base_module, "=="); jl_value_t *nothing = static_cast(jl_eval_string("nothing")); bool is_nothing = jl_unbox_bool( static_cast(jl_call2(equality, nothing, val)) ); if (!is_nothing) { jl_value_t *out_display = static_cast(jl_eval_string("TextDisplay(" JULIA_STDOUT ")")); jl_function_t *display = jl_get_function(jl_base_module, "display"); jl_call2(display, out_display, val); } m_was_exception = false; } // Clean up streams and files jl_eval_string("flush(" JULIA_STDOUT ")"); jl_eval_string("flush(" JULIA_STDERR ")"); jl_eval_string("redirect_stdout(__originalSTDOUT__)"); jl_eval_string("redirect_stderr(__originalSTDERR__)"); // Clean up variables auto vars_to_remove = { "__originalSTDOUT__", "__originalSTDERR__" }; for (const auto &var : vars_to_remove) { jl_eval_string( QString::fromLatin1("%1 = 0").arg(QLatin1String(var)) .toLatin1().constData() ); } m_output = QString::fromUtf8(output.readAll()); m_error = QString::fromUtf8(error.readAll()); } QString JuliaServer::getError() const { return m_error; } QString JuliaServer::getOutput() const { return m_output; } bool JuliaServer::getWasException() const { return m_was_exception; } #if QT_VERSION_CHECK(JULIA_VERSION_MAJOR, JULIA_VERSION_MINOR, 0) >= QT_VERSION_CHECK(1, 1, 0) #define JL_MAIN_MODULE jl_main_module #else #define JL_MAIN_MODULE jl_internal_main_module #endif -void JuliaServer::parseModules() +void JuliaServer::parseModules(bool variableManagement) { - parseJlModule(JL_MAIN_MODULE); + parseJlModule(JL_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"); if (module != JL_MAIN_MODULE) { const QString& moduleName = fromJuliaString(jl_call1(jl_string_function, (jl_value_t*)(module->name))); if (parsedModules.contains(moduleName)) return; else parsedModules.append(moduleName); } jl_function_t* jl_names_function = jl_get_function(jl_base_module, "names"); jl_value_t* names = jl_call1(jl_names_function, (jl_value_t*)module); jl_value_t **data = (jl_value_t**)jl_array_data(names); for (size_t i = 0; i < jl_array_len(names); i++) { bool isBindingResolved = (bool)jl_binding_resolved_p(module, (jl_sym_t*)(data[i])); if (isBindingResolved) { const QString& name = fromJuliaString(jl_call1(jl_string_function, data[i])); jl_value_t* value = jl_get_binding_or_error(module, (jl_sym_t*)(data[i]))->value; jl_datatype_t* datetype = (jl_datatype_t*)jl_typeof(value); QString type = QString::fromUtf8(jl_typeof_str(value)); // Module if (jl_is_module(value)) { if (module == JL_MAIN_MODULE && (jl_module_t*)value != JL_MAIN_MODULE) - parseJlModule((jl_module_t*)value); + parseJlModule((jl_module_t*)value, parseValue); } // Function else if (type.startsWith(QLatin1String("#")) || type == QLatin1String("Function")) { if (!m_functions.contains(name)) m_functions.append(name); } // Variable else if (datetype != jl_datatype_type) // Not type { if (module == JL_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); } } } } } } QString JuliaServer::fromJuliaString(const jl_value_t* value) { return QString::fromUtf8(jl_string_data(value)); } QStringList JuliaServer::variablesList() { return m_variables; } QStringList JuliaServer::variableValuesList() { return m_variableValues; } QStringList JuliaServer::functionsList() { return m_functions; } diff --git a/src/backends/julia/juliaserver/juliaserver.h b/src/backends/julia/juliaserver/juliaserver.h index 98d8f4d3..3c39d00f 100644 --- a/src/backends/julia/juliaserver/juliaserver.h +++ b/src/backends/julia/juliaserver/juliaserver.h @@ -1,109 +1,111 @@ /* * 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) 2016 Ivan Lakhtanov #include #include #include #include /** * Implementation of command execution server with DBus interface for Julia * language. * * Uses Julia embedding * http://docs.julialang.org/en/release-0.4/manual/embedding/ to get results. */ class JuliaServer: public QObject { Q_OBJECT public: explicit JuliaServer(QObject *parent = nullptr); ~JuliaServer() override; public Q_SLOTS: /** * Initializer for JuliaServer. Call this first before using it * * @param path path to julia executable */ Q_SCRIPTABLE void login(const QString &path) const; /** * Runs a piece of julia code. After this returns use getOutput, getError, * getWasException methods to retrieve execution result. * * @param command maybe multiline piece of julia code to run */ Q_SCRIPTABLE void runJuliaCommand(const QString &command); /** * @return stdout output of the last command execution */ Q_SCRIPTABLE QString getOutput() const; /** * @return stderr output of the last command execution */ Q_SCRIPTABLE QString getError() const; /** * @return indicator that exception was triggered during last command * execution */ Q_SCRIPTABLE bool getWasException() const; /** * 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 */ Q_SCRIPTABLE QStringList variablesList(); /** * @return corresponding list of values for variables from variablesList. */ Q_SCRIPTABLE QStringList variableValuesList(); /** * @return list of function in internal Julia's module */ 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: QString m_error; //< Stores last stderr output QString m_output; //< Stores last stdout output bool m_was_exception; //< Stores indicator of exception QStringList parsedModules; QStringList m_variables; QStringList m_variableValues; QStringList m_functions; static QStringList INTERNAL_VARIABLES; }; diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp index da30f2de..431d899a 100644 --- a/src/backends/julia/juliasession.cpp +++ b/src/backends/julia/juliasession.cpp @@ -1,286 +1,243 @@ /* 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) 2016 Ivan Lakhtanov */ #include "juliasession.h" #include #include #include #include #include #include "defaultvariablemodel.h" #include "juliaexpression.h" #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) { } void JuliaSession::login() { emit loginStarted(); if (m_process) { m_process->deleteLater(); } m_process = new KProcess(this); m_process->setOutputChannelMode(KProcess::SeparateChannels); (*m_process) << QStandardPaths::findExecutable(QLatin1String("cantor_juliaserver")); m_process->start(); m_process->waitForStarted(); m_process->waitForReadyRead(); QTextStream stream(m_process->readAllStandardOutput()); QString readyStatus = QLatin1String("ready"); while (m_process->state() == QProcess::Running) { const QString &rl = stream.readLine(); if (rl == readyStatus) { break; } } if (!QDBusConnection::sessionBus().isConnected()) { qWarning() << "Can't connect to the D-Bus session bus.\n" "To start it, run: eval `dbus-launch --auto-syntax`"; return; } const QString &serviceName = QString::fromLatin1("org.kde.Cantor.Julia-%1").arg(m_process->pid()); m_interface = new QDBusInterface( serviceName, QString::fromLatin1("/"), QString(), QDBusConnection::sessionBus() ); if (!m_interface->isValid()) { qWarning() << QDBusConnection::sessionBus().lastError().message(); return; } m_interface->call( QString::fromLatin1("login"), JuliaSettings::self()->replPath().path() ); - listVariables(); + m_variableModel->setJuliaServer(m_interface); + m_variableModel->update(); // Plots integration if (integratePlots()) { runJuliaCommand( QLatin1String("import GR; ENV[\"GKS_WSTYPE\"] = \"nul\"") ); } changeStatus(Session::Done); emit loginDone(); qDebug() << "login to julia " << JULIA_VERSION_STRING << "done"; } void JuliaSession::logout() { m_process->terminate(); - JuliaKeywords::instance()->clearVariables(); - JuliaKeywords::instance()->clearFunctions(); m_variableModel->clearVariables(); - emit updateHighlighter(); changeStatus(Status::Disable); } void JuliaSession::interrupt() { if (m_process->pid()) { m_process->kill(); } for (Cantor::Expression *e : m_runningExpressions) { e->interrupt(); } m_runningExpressions.clear(); changeStatus(Cantor::Session::Done); } Cantor::Expression *JuliaSession::evaluateExpression( const QString &cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { JuliaExpression *expr = new JuliaExpression(this, internal); changeStatus(Cantor::Session::Running); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } Cantor::CompletionObject *JuliaSession::completionFor( const QString &command, int index) { return new JuliaCompletionObject(command, index, this); } QSyntaxHighlighter *JuliaSession::syntaxHighlighter(QObject *parent) { - JuliaHighlighter *highlighter = new JuliaHighlighter(parent); - QObject::connect( - this, SIGNAL(updateHighlighter()), highlighter, SLOT(updateHighlight()) - ); - return highlighter; + return new JuliaHighlighter(parent, this); } void JuliaSession::runJuliaCommand(const QString &command) const { m_interface->call(QLatin1String("runJuliaCommand"), command); } void JuliaSession::runJuliaCommandAsync(const QString &command) { m_interface->callWithCallback( QLatin1String("runJuliaCommand"), {command}, this, SLOT(onResultReady()) ); } 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); } void JuliaSession::runExpression(JuliaExpression *expr) { m_runningExpressions.append(expr); m_currentExpression = expr; runJuliaCommandAsync(expr->internalCommand()); } QString JuliaSession::getStringFromServer(const QString &method) { const QDBusReply &reply = m_interface->call(method); return (reply.isValid() ? reply.value() : reply.error().message()); } QString JuliaSession::getOutput() { return getStringFromServer(QLatin1String("getOutput")); } QString JuliaSession::getError() { return getStringFromServer(QLatin1String("getError")); } bool JuliaSession::getWasException() { const QDBusReply &reply = m_interface->call(QLatin1String("getWasException")); 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() +Cantor::DefaultVariableModel* JuliaSession::variableModel() const { return m_variableModel; } bool JuliaSession::integratePlots() { return JuliaSettings::integratePlots(); } diff --git a/src/backends/julia/juliasession.h b/src/backends/julia/juliasession.h index a6dab984..e98a1a51 100644 --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -1,175 +1,165 @@ /* 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) 2016 Ivan Lakhtanov */ #pragma once #include #include #include "session.h" class JuliaExpression; class JuliaCompletionObject; +class JuliaVariableModel; class KProcess; class QDBusInterface; namespace Cantor { class DefaultVariableModel; } /** * Implements a Cantor session for the Julia backend * * It communicates through DBus interface with JuliaServer */ class JuliaSession: public Cantor::Session { Q_OBJECT public: /** * Constructs session * * @param backend owning backend */ explicit JuliaSession(Cantor::Backend *backend); /** * @see Cantor::Session::login */ void login() override; /** * @see Cantor::Session::logout */ void logout() override; /** * @see Cantor::Session::interrupt */ void interrupt() override; /** * @see Cantor::Session::evaluateExpression */ Cantor::Expression *evaluateExpression( const QString &command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; /** * @see Cantor::Session::completionFor */ Cantor::CompletionObject *completionFor( const QString &cmd, int index = -1) override; /** * @see Cantor::Session::syntaxHighlighter */ QSyntaxHighlighter *syntaxHighlighter(QObject *parent) override; /** * @see Cantor::Session::variableModel */ - QAbstractItemModel *variableModel() override; + Cantor::DefaultVariableModel *variableModel() const override; /** * @return indicator if config says to integrate plots into worksheet */ bool integratePlots(); -Q_SIGNALS: - /** - * Emit this to update syntax highlighter - */ - void updateHighlighter(); - private Q_SLOTS: /** * Called when async call to JuliaServer is finished */ void onResultReady(); private: KProcess *m_process; //< process to run JuliaServer inside QDBusInterface *m_interface; //< interface to JuliaServer /// Expressions running at the moment QList m_runningExpressions; 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; friend JuliaExpression; friend JuliaCompletionObject; /** * Runs Julia expression * * @param expression expression to run */ void runExpression(JuliaExpression *expression); /** * Runs Julia piece of code in synchronous mode * * @param command command to execute */ void runJuliaCommand(const QString &command) const; /** * Runs Julia piece of code in asynchronous mode. When finished * onResultReady is called * * @param command command to execute */ void runJuliaCommandAsync(const QString &command); /** * Helper method to get QString returning function result * * @param method DBus method to call * @return result of the method */ QString getStringFromServer(const QString &method); /** * @return stdout of the last executed command */ QString getOutput(); /** * @return stderr of the last executed command */ QString getError(); /** * @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/juliavariablemodel.cpp b/src/backends/julia/juliavariablemodel.cpp new file mode 100644 index 00000000..5ed17e58 --- /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/python/pythonserver.h b/src/backends/julia/juliavariablemodel.h similarity index 55% copy from src/backends/python/pythonserver.h copy to src/backends/julia/juliavariablemodel.h index b4cd0f49..7ebd8a85 100644 --- a/src/backends/python/pythonserver.h +++ b/src/backends/julia/juliavariablemodel.h @@ -1,47 +1,50 @@ /* 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) 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/settings.ui b/src/backends/julia/settings.ui index f7026c7c..8813485c 100644 --- a/src/backends/julia/settings.ui +++ b/src/backends/julia/settings.ui @@ -1,81 +1,91 @@ JuliaSettingsBase Path to Julia REPL: + + + + + Let Cantor follow the creation/destruction of variables + + + Enable Variable Management + + Integrate Plots in Worksheet (start a new session when changed) Inline Plots Intermediate Format: svg eps png Qt::Vertical 20 40 KUrlRequester QFrame
kurlrequester.h
diff --git a/src/backends/julia/testjulia.cpp b/src/backends/julia/testjulia.cpp index 61bbe959..b5c233ef 100644 --- a/src/backends/julia/testjulia.cpp +++ b/src/backends/julia/testjulia.cpp @@ -1,294 +1,290 @@ /* * 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) 2016 Ivan Lakhtanov */ #include "testjulia.h" #include "session.h" #include "backend.h" #include "expression.h" #include "result.h" #include "textresult.h" #include "imageresult.h" #include "defaultvariablemodel.h" #include "completionobject.h" QString TestJulia::backendName() { return QLatin1String("julia"); } void TestJulia::testOneLine() { Cantor::Expression *e = evalExp(QLatin1String("2 + 3")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("5")); QVERIFY(e->errorMessage().isEmpty()); } void TestJulia::testOneLineWithPrint() { Cantor::Expression *e = evalExp(QLatin1String("print(2 + 3)")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("5")); QVERIFY(e->errorMessage().isEmpty()); } void TestJulia::testException() { Cantor::Expression *e = evalExp(QLatin1String("sqrt(-1)")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Error); QVERIFY(e->result()); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QVERIFY( !e->errorMessage().isEmpty() && e->errorMessage().contains(QLatin1String( "sqrt will only return a complex result if called with a " "complex argument." )) ); } void TestJulia::testSyntaxError() { Cantor::Expression *e = evalExp(QLatin1String("for i = 1:10\nprint(i)")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Error); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QVERIFY( !e->errorMessage().isEmpty() && e->errorMessage().contains(QLatin1String( "syntax: incomplete: \"for\" at none:1 requires end" )) ); } void TestJulia::testMultilineCode() { Cantor::Expression *e = evalExp(QLatin1String( "q = 0; # comment\n" "# sdfsdf\n" "for i = 1:10\n" " global q\n" " q += i\n" "end\n" "print(q)" )); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("55")); QVERIFY(e->errorMessage().isEmpty()); } void TestJulia::testPartialResultOnException() { Cantor::Expression *e = evalExp(QLatin1String( "for i = 1:5\n" " print(i)\n" "end\n" "sqrt(-1)\n" )); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Error); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("12345")); QVERIFY( !e->errorMessage().isEmpty() && e->errorMessage().contains(QLatin1String( "sqrt will only return a complex result if called with a " "complex argument." )) ); } void TestJulia::testInlinePlot() { Cantor::Expression *e = evalExp(QLatin1String( "import GR\n" "GR.plot([-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1], sin)" )); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); QVERIFY(e->result()->type() == Cantor::ImageResult::Type); } void TestJulia::testInlinePlotWithExceptionAndPartialResult() { Cantor::Expression *e = evalExp(QLatin1String( "import GR\n" "print(\"gonna plot\")\n" "sqrt(-1)\n" "GR.plot([-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1], sin)\n" )); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Error); QVERIFY(e->result()->type() == Cantor::TextResult::Type); QCOMPARE(e->result()->data().toString(), QLatin1String("gonna plot")); QVERIFY( !e->errorMessage().isEmpty() && e->errorMessage().contains(QLatin1String( "sqrt will only return a complex result if called with a " "complex argument." )) ); } void TestJulia::testAddVariablesFromCode() { evalExp(QLatin1String("a = 0; b = 1; c = 2; d = 3\n")); auto variableModel = session()->variableModel(); QStringList variableNames = QString::fromLatin1("a b c d").split(QLatin1String(" ")); for (int i = 0; i < variableNames.size(); i++) { QModelIndexList matchedVariables = variableModel->match( variableModel->index(0, 0), Qt::DisplayRole, QVariant::fromValue(variableNames[i]), -1, Qt::MatchExactly ); QCOMPARE(matchedVariables.size(), 1); auto match = matchedVariables[0]; auto valueIndex = variableModel->index(match.row(), match.column() + 1); QVERIFY( valueIndex.data(Qt::DisplayRole) == QVariant::fromValue(QString::number(i)) ); } } void TestJulia::testAddVariablesFromManager() { - auto variableModel = static_cast( - session()->variableModel() - ); + Cantor::DefaultVariableModel* variableModel = session()->variableModel(); QStringList variableNames = QString::fromLatin1("a b c d").split(QLatin1String(" ")); for (int i = 0; i < variableNames.size(); i++) { variableModel->addVariable(variableNames[i], QString::number(i)); QModelIndexList matchedVariables = variableModel->match( variableModel->index(0, 0), Qt::DisplayRole, QVariant::fromValue(variableNames[i]), -1, Qt::MatchExactly ); QCOMPARE(matchedVariables.size(), 1); auto match = matchedVariables[0]; auto valueIndex = variableModel->index(match.row(), match.column() + 1); QVERIFY( valueIndex.data(Qt::DisplayRole) == QVariant::fromValue(QString::number(i)) ); } } void TestJulia::testRemoveVariables() { - auto variableModel = static_cast( - session()->variableModel() - ); + Cantor::DefaultVariableModel* variableModel = session()->variableModel(); QStringList variableNames = QString::fromLatin1("a b c d").split(QLatin1String(" ")); for (int i = 0; i < variableNames.size(); i++) { variableModel->addVariable(variableNames[i], QString::number(i)); } for (int i = 0; i < variableNames.size(); i += 2) { variableModel->removeVariable(variableNames[i]); } for (int i = 0; i < variableNames.size(); i++) { QModelIndexList matchedVariables = variableModel->match( variableModel->index(0, 0), Qt::DisplayRole, QVariant::fromValue(variableNames[i]), -1, Qt::MatchExactly ); if (i % 2 == 0) { QVERIFY(matchedVariables.isEmpty()); } else { QCOMPARE(matchedVariables.size(), 1); auto match = matchedVariables[0]; auto valueIndex = variableModel->index(match.row(), match.column() + 1); QVERIFY( valueIndex.data(Qt::DisplayRole) == QVariant::fromValue(QString::number(i)) ); } } } void TestJulia::testAutoCompletion() { auto prefix = QLatin1String("ex"); auto completionObject = session()->completionFor(prefix); waitForSignal(completionObject, SIGNAL(fetchingDone())); auto completions = completionObject->completions(); QStringList completionsToCheck; completionsToCheck << QLatin1String("exit"); completionsToCheck << QLatin1String("exponent"); completionsToCheck << QLatin1String("exp"); for (auto completion : completionsToCheck) { QVERIFY(completions.contains(completion)); } for (auto completion : completions) { QVERIFY(completion.startsWith(prefix)); } } void TestJulia::testComplexAutocompletion() { auto prefix = QLatin1String("Base.Ma"); auto completionObject = session()->completionFor(prefix); waitForSignal(completionObject, SIGNAL(fetchingDone())); auto completions = completionObject->completions(); QStringList completionsToCheck; completionsToCheck << QLatin1String("Base.MainInclude"); completionsToCheck << QLatin1String("Base.Math"); completionsToCheck << QLatin1String("Base.Matrix"); for (auto completion : completionsToCheck) { QVERIFY(completions.contains(completion)); } for (auto completion : completions) { QVERIFY(completion.startsWith(prefix)); } } QTEST_MAIN(TestJulia) diff --git a/src/backends/kalgebra/kalgebrasession.cpp b/src/backends/kalgebra/kalgebrasession.cpp index ebdf0492..c02aee01 100644 --- a/src/backends/kalgebra/kalgebrasession.cpp +++ b/src/backends/kalgebra/kalgebrasession.cpp @@ -1,114 +1,114 @@ /************************************************************************************* * Copyright (C) 2009 by Aleix Pol * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "kalgebrasession.h" #include "settings.h" #include "kalgebraexpression.h" #include "kalgebracompletionobject.h" #include #include #include #include #include "kalgebrasyntaxhelpobject.h" #include #include KAlgebraSession::KAlgebraSession( Cantor::Backend* backend) : Session(backend) { m_analyzer = new Analitza::Analyzer; m_operatorsModel = new OperatorsModel; m_variablesModel = new Analitza::VariablesModel(m_analyzer->variables()); m_operatorsModel->setVariables(m_analyzer->variables()); } KAlgebraSession::~KAlgebraSession() { delete m_analyzer; } void KAlgebraSession::login() { emit loginStarted(); if(!KAlgebraSettings::autorunScripts().isEmpty()){ QString autorunScripts = KAlgebraSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, KAlgebraExpression::DeleteOnFinish, true); } changeStatus(Cantor::Session::Done); emit loginDone(); } void KAlgebraSession::logout() { changeStatus(Status::Disable); } void KAlgebraSession::interrupt() { changeStatus(Cantor::Session::Done); } Cantor::Expression* KAlgebraSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { KAlgebraExpression* expr=new KAlgebraExpression(this, internal); expr->setFinishingBehavior(behave); changeStatus(Cantor::Session::Running); expr->setCommand(cmd); expr->evaluate(); changeStatus(Cantor::Session::Done); m_operatorsModel->setVariables(m_analyzer->variables()); m_variablesModel->updateInformation(); return expr; } Cantor::CompletionObject* KAlgebraSession::completionFor(const QString& command, int index) { return new KAlgebraCompletionObject(command, index, this); } Cantor::SyntaxHelpObject* KAlgebraSession::syntaxHelpFor(const QString& cmd) { return new KAlgebraSyntaxHelpObject(cmd, this); } OperatorsModel* KAlgebraSession::operatorsModel() { return m_operatorsModel; } QSyntaxHighlighter* KAlgebraSession::syntaxHighlighter(QObject* parent) { Q_UNUSED(parent); //return new AlgebraHighlighter(parent->document()); // TODO: Think of something better here. return new AlgebraHighlighter(nullptr); } -QAbstractItemModel* KAlgebraSession::variableModel() +QAbstractItemModel* KAlgebraSession::variableDataModel() const { return m_variablesModel; } diff --git a/src/backends/kalgebra/kalgebrasession.h b/src/backends/kalgebra/kalgebrasession.h index 432f38a7..29fea8cf 100644 --- a/src/backends/kalgebra/kalgebrasession.h +++ b/src/backends/kalgebra/kalgebrasession.h @@ -1,58 +1,58 @@ /************************************************************************************* * Copyright (C) 2009 by Aleix Pol * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef KALGEBRA_SESSION_H #define KALGEBRA_SESSION_H #include "session.h" class OperatorsModel; class KAlgebraExpression; namespace Analitza { class Analyzer; class VariablesModel; } class KAlgebraSession : public Cantor::Session { Q_OBJECT public: explicit KAlgebraSession( Cantor::Backend* backend); ~KAlgebraSession() override; void login() override; void logout() override; void interrupt() override; Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& cmd) override; Analitza::Analyzer* analyzer() const { return m_analyzer; } OperatorsModel* operatorsModel(); QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; - QAbstractItemModel* variableModel() override; + QAbstractItemModel* variableDataModel() const override; private: Analitza::Analyzer* m_analyzer; OperatorsModel* m_operatorsModel; Analitza::VariablesModel* m_variablesModel; }; #endif diff --git a/src/backends/maxima/maximacompletionobject.cpp b/src/backends/maxima/maximacompletionobject.cpp index b85eac22..9559307d 100644 --- a/src/backends/maxima/maximacompletionobject.cpp +++ b/src/backends/maxima/maximacompletionobject.cpp @@ -1,82 +1,79 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009-2012 Alexander Rieder */ #include "maximacompletionobject.h" #include #include "maximasession.h" #include "maximakeywords.h" #include "maximavariablemodel.h" MaximaCompletionObject::MaximaCompletionObject(const QString& command, int index,MaximaSession* session) : Cantor::CompletionObject(session) { qDebug() << "MaximaCompletionObject constructor"; setLine(command, index); } void MaximaCompletionObject::fetchIdentifierType() { - MaximaVariableModel* model=static_cast(session()->variableModel()); - QStringList userVariableNames=model->variableNames(); - QStringList userFunctionNames=model->functionNames(true); + QStringList userVariableNames=session()->variableModel()->variableNames(); + QStringList userFunctionNames=session()->variableModel()->functions(); if (qBinaryFind(userVariableNames.begin(), userVariableNames.end(), identifier()) != userVariableNames.end()) emit fetchingTypeDone(VariableType); else if (qBinaryFind(userFunctionNames.begin(), userFunctionNames.end(), identifier()) != userFunctionNames.end()) emit fetchingTypeDone(FunctionType); else if (qBinaryFind(MaximaKeywords::instance()->functions().begin(), MaximaKeywords::instance()->functions().end(), identifier()) != MaximaKeywords::instance()->functions().end()) emit fetchingTypeDone(FunctionType); else if (qBinaryFind(MaximaKeywords::instance()->keywords().begin(), MaximaKeywords::instance()->keywords().end(), identifier()) != MaximaKeywords::instance()->keywords().end()) emit fetchingTypeDone(KeywordType); else emit fetchingTypeDone(VariableType); } void MaximaCompletionObject::fetchCompletions() { - MaximaVariableModel* model=static_cast(session()->variableModel()); - QStringList allCompletions; allCompletions<variables(); allCompletions<functions(); allCompletions<keywords(); - allCompletions<variableNames(); - allCompletions<functionNames(true); + allCompletions<variableModel()->variableNames(); + allCompletions<variableModel()->functions(); setCompletions(allCompletions); emit fetchingDone(); } bool MaximaCompletionObject::mayIdentifierContain(QChar c) const { return c.isLetter() || c.isDigit() || c == QLatin1Char('_') || c == QLatin1Char('%'); } bool MaximaCompletionObject::mayIdentifierBeginWith(QChar c) const { return c.isLetter() || c == QLatin1Char('_') || c == QLatin1Char('%'); } diff --git a/src/backends/maxima/maximahighlighter.cpp b/src/backends/maxima/maximahighlighter.cpp index 87d51ea6..da183f66 100644 --- a/src/backends/maxima/maximahighlighter.cpp +++ b/src/backends/maxima/maximahighlighter.cpp @@ -1,147 +1,108 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009-2012 Alexander Rieder */ #include "maximahighlighter.h" #include "maximakeywords.h" #include "maximasession.h" #include "maximavariablemodel.h" -MaximaHighlighter::MaximaHighlighter(QObject* parent, MaximaSession* session) : Cantor::DefaultHighlighter(parent) +MaximaHighlighter::MaximaHighlighter(QObject* parent, MaximaSession* session) + : Cantor::DefaultHighlighter(parent, session) { //addRule(QRegExp("\\b[A-Za-z0-9_]+(?=\\()"), functionFormat()); //Code highlighting the different keywords addKeywords(MaximaKeywords::instance()->keywords()); addRule(QLatin1String("FIXME"), commentFormat()); addRule(QLatin1String("TODO"), commentFormat()); addFunctions(MaximaKeywords::instance()->functions()); addVariables(MaximaKeywords::instance()->variables()); //addRule(QRegExp("\".*\""), stringFormat()); //addRule(QRegExp("'.*'"), stringFormat()); commentStartExpression = QRegExp(QLatin1String("/\\*")); commentEndExpression = QRegExp(QLatin1String("\\*/")); - - connect(session->variableModel(), SIGNAL(variablesAdded(QStringList)), this, SLOT(addUserVariables(QStringList))); - connect(session->variableModel(), SIGNAL(variablesRemoved(QStringList)), this, SLOT(removeUserVariables(QStringList))); - connect(session->variableModel(), SIGNAL(functionsAdded(QStringList)), this, SLOT(addUserFunctions(QStringList))); - connect(session->variableModel(), SIGNAL(functionsRemoved(QStringList)), this, SLOT(removeUserFunctions(QStringList))); - - MaximaVariableModel* model=static_cast(session->variableModel()); - addUserVariables(model->variableNames()); - addUserFunctions(model->functionNames()); } void MaximaHighlighter::highlightBlock(const QString& text) { if (skipHighlighting(text)) return; //Do some backend independent highlighting (brackets etc.) DefaultHighlighter::highlightBlock(text); setCurrentBlockState(-1); int commentLevel = 0; bool inString = false; int startIndex = -1; if (previousBlockState() > 0) { commentLevel = previousBlockState(); startIndex = 0; } else if (previousBlockState() < -1) { inString = true; startIndex = 0; } for (int i = 0; i < text.size(); ++i) { if (text[i] == QLatin1Char('\\')) { ++i; // skip the next character } else if (text[i] == QLatin1Char('"') && commentLevel == 0) { if (!inString) startIndex = i; else setFormat(startIndex, i - startIndex + 1, stringFormat()); inString = !inString; } else if (text.mid(i,2) == QLatin1String("/*") && !inString) { if (commentLevel == 0) startIndex = i; ++commentLevel; ++i; } else if (text.mid(i,2) == QLatin1String("*/") && !inString) { if (commentLevel == 0) { setFormat(i, 2, errorFormat()); // undo the --commentLevel below, so we stay at 0 ++commentLevel; } else if (commentLevel == 1) { setFormat(startIndex, i - startIndex + 2, commentFormat()); } ++i; --commentLevel; } } if (inString) { setCurrentBlockState(-2); setFormat(startIndex, text.size() - startIndex, stringFormat()); } else if (commentLevel > 0) { setCurrentBlockState(commentLevel); setFormat(startIndex, text.size() - startIndex, commentFormat()); } } -void MaximaHighlighter::addUserVariables(const QStringList variables) -{ - addVariables(variables); -} - -void MaximaHighlighter::removeUserVariables(const QStringList variables) -{ - for (const QString& var : variables) - removeRule(var); -} - -void MaximaHighlighter::addUserFunctions(const QStringList functions) -{ - //remove the trailing (x) - for (const QString& func : functions) - { - int idx=func.lastIndexOf(QLatin1Char('(')); - addRule(func.left(idx), functionFormat()); - } -} - -void MaximaHighlighter::removeUserFunctions(const QStringList functions) -{ - //remove the trailing (x) - for (const QString& func : functions) - { - int idx=func.lastIndexOf(QLatin1Char('(')); - removeRule(func.left(idx)); - } -} - QString MaximaHighlighter::nonSeparatingCharacters() const { return QLatin1String("%"); } diff --git a/src/backends/maxima/maximahighlighter.h b/src/backends/maxima/maximahighlighter.h index 27d573df..7f38f196 100644 --- a/src/backends/maxima/maximahighlighter.h +++ b/src/backends/maxima/maximahighlighter.h @@ -1,50 +1,44 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _MAXIMAHIGHLIGHTER_H #define _MAXIMAHIGHLIGHTER_H #include "defaulthighlighter.h" class MaximaSession; class MaximaHighlighter : public Cantor::DefaultHighlighter { Q_OBJECT public: MaximaHighlighter( QObject* parent, MaximaSession* session); ~MaximaHighlighter() override = default; protected: void highlightBlock(const QString &text) override; QString nonSeparatingCharacters() const override; - private Q_SLOTS: - void addUserVariables(const QStringList variables); - void removeUserVariables(const QStringList variables); - - void addUserFunctions(const QStringList functions); - void removeUserFunctions(const QStringList functions); private: QRegExp commentStartExpression; QRegExp commentEndExpression; }; #endif /* _MAXIMAHIGHLIGHTER_H */ diff --git a/src/backends/maxima/maximasession.cpp b/src/backends/maxima/maximasession.cpp index b65da27e..c12ec3fe 100644 --- a/src/backends/maxima/maximasession.cpp +++ b/src/backends/maxima/maximasession.cpp @@ -1,343 +1,332 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009-2012 Alexander Rieder Copyright (C) 2017-2018 Alexander Semke (alexander.semke@web.de) */ #include "maximasession.h" #include "maximaexpression.h" #include "maximacompletionobject.h" #include "maximasyntaxhelpobject.h" #include "maximahighlighter.h" #include "maximavariablemodel.h" #include "result.h" #include "settings.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif //NOTE: the \\s in the expressions is needed, because Maxima seems to sometimes insert newlines/spaces between the letters //maybe this is caused by some behaviour if the Prompt is split into multiple "readStdout" calls //the Expressions are encapsulated in () to allow capturing for the text const QRegExp MaximaSession::MaximaOutputPrompt=QRegExp(QLatin1String("(\\(\\s*%\\s*o\\s*[0-9\\s]*\\))")); //Text, maxima outputs, before any output const QRegExp MaximaSession::MaximaInputPrompt = QRegExp(QLatin1String("(\\(\\s*%\\s*i\\s*[0-9\\s]*\\))")); 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() { qDebug()<<"login"; if (m_process) return; //TODO: why do we call login() again?!? emit loginStarted(); QStringList arguments; arguments << QLatin1String("--quiet"); //Suppress Maxima start-up message const QString initFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/maximabackend/cantor-initmaxima.lisp")); arguments << QLatin1String("--init-lisp=") + initFile; //Set the name of the Lisp initialization file m_process = new QProcess(this); m_process->start(MaximaSettings::self()->path().toLocalFile(), arguments); m_process->waitForStarted(); QString input; // Wait until first maxima prompt while (!input.contains(QLatin1String(""))) { m_process->waitForReadyRead(); input += QString::fromLatin1(m_process->readAllStandardOutput()); qDebug() << input; } connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(restartMaxima())); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStdOut())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); if(!MaximaSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = MaximaSettings::self()->autorunScripts().join(QLatin1String(";")); autorunScripts.append(QLatin1String(";kill(labels)")); // Reset labels after running autorun scripts evaluateExpression(autorunScripts, MaximaExpression::DeleteOnFinish, true); + updateVariables(); } changeStatus(Session::Done); emit loginDone(); qDebug()<<"login done"; } void MaximaSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, nullptr, this, nullptr); // if(status()==Cantor::Session::Running) //TODO: terminate the running expressions first write(QLatin1String("quit();\n")); qDebug()<<"waiting for maxima to finish"; m_process->waitForFinished(); qDebug()<<"maxima exit finished"; if(m_process->state() != QProcess::NotRunning) { m_process->kill(); qDebug()<<"maxima still running, process kill enforced"; } expressionQueue().clear(); delete m_process; m_process = nullptr; - m_variableModel->clear(); + variableModel()->clearVariables(); + variableModel()->clearFunctions(); changeStatus(Status::Disable); qDebug()<<"logout done"; } Cantor::Expression* MaximaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; MaximaExpression* expr = new MaximaExpression(this, internal); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void MaximaSession::readStdErr() { qDebug()<<"reading stdErr"; if (!m_process) return; QString out=QLatin1String(m_process->readAllStandardError()); if(expressionQueue().size()>0) { MaximaExpression* expr = static_cast(expressionQueue().first()); expr->parseError(out); } } void MaximaSession::readStdOut() { QString out = QLatin1String(m_process->readAllStandardOutput()); m_cache += out; //collect the multi-line output until Maxima has finished the calculation and returns a new promt if ( !out.contains(QLatin1String("")) ) return; if(expressionQueue().isEmpty()) { //queue is empty, interrupt was called, nothing to do here qDebug()<(expressionQueue().first()); if (!expr) return; //should never happen qDebug()<<"output: " << m_cache; expr->parseOutput(m_cache); m_cache.clear(); } void MaximaSession::reportProcessError(QProcess::ProcessError e) { qDebug()<<"process error"<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; } } void MaximaSession::runFirstExpression() { qDebug()<<"running next expression"; if (!m_process) return; if(!expressionQueue().isEmpty()) { MaximaExpression* expr = static_cast(expressionQueue().first()); QString command=expr->internalCommand(); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); expr->setStatus(Cantor::Expression::Computing); if(command.isEmpty()) { qDebug()<<"empty command"; expr->forceDone(); } else { m_cache.clear(); write(command + QLatin1Char('\n')); } } } void MaximaSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); m_cache.clear(); } void MaximaSession::sendInputToProcess(const QString& input) { write(input); } void MaximaSession::restartMaxima() { qDebug()<<"restarting maxima cooldown: "<write(exp.toUtf8()); } diff --git a/src/backends/maxima/maximasession.h b/src/backends/maxima/maximasession.h index 612f6fae..e6271853 100644 --- a/src/backends/maxima/maximasession.h +++ b/src/backends/maxima/maximasession.h @@ -1,75 +1,73 @@ /* Tims program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009-2012 Alexander Rieder */ #ifndef _MAXIMASESSION_H #define _MAXIMASESSION_H #include "session.h" #include "expression.h" #include class MaximaExpression; class MaximaVariableModel; class MaximaSession : public Cantor::Session { Q_OBJECT public: static const QRegExp MaximaOutputPrompt; static const QRegExp MaximaInputPrompt; explicit MaximaSession( Cantor::Backend* backend); void login() override; void logout() override; Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; void interrupt() override; void sendInputToProcess(const QString&); void setTypesettingEnabled(bool) override; 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: void readStdOut(); void readStdErr(); private Q_SLOTS: void currentExpressionChangedStatus(Cantor::Expression::Status); void restartMaxima(); void restartsCooledDown(); void reportProcessError(QProcess::ProcessError); private: void write(const QString&); QProcess* m_process; QString m_cache; - MaximaVariableModel* m_variableModel; bool m_justRestarted; }; #endif /* _MAXIMASESSION_H */ diff --git a/src/backends/maxima/maximavariablemodel.cpp b/src/backends/maxima/maximavariablemodel.cpp index f449741c..4d1c77b4 100644 --- a/src/backends/maxima/maximavariablemodel.cpp +++ b/src/backends/maxima/maximavariablemodel.cpp @@ -1,279 +1,182 @@ /* 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) 2012 Alexander Rieder */ #include "maximavariablemodel.h" #include "maximasession.h" #include "maximaexpression.h" #include "textresult.h" #include "latexresult.h" #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 = static_cast(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 = static_cast(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(); } //for parsing of names and values below (old code) we need to combine multiple results back to one string QString text; for (auto* result : expr->results()) { if(result->type()==Cantor::TextResult::Type) text += dynamic_cast(result)->plain(); else if(expr->result()->type()==Cantor::LatexResult::Type) text += dynamic_cast(result)->plain(); } const int nameIndex=text.indexOf(QLatin1Char(']')); QString namesString=text.left(nameIndex); //namesString.chop(1); namesString=namesString.mid(1); namesString=namesString.trimmed(); qDebug()<<"variable names: "<(); QStringList variableNames; QString valuesString; bool hasValues = false; QStringList variableValues; if ( namesString.contains(QLatin1Char(')')) ) { //function definition(s): e.g //text = "[f1(x),f2(x,y),f3(x,y,z)]\n$DONE" //nameString = f1(x),f2(x,y),f3(x,y,z) //variableString = "\n$DONE" variableNames = namesString.split(QLatin1String("),")); } else { //variable definition(s): e.g. //text = "[a,b]\n1\n\"-cantor-value-separator-\"\n2\n\"-cantor-value-separator-\"\n($A $B)" //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()< variables; variables.reserve(variableNames.size()); for(int i=0;ii) { var.value=variableValues.at(i).trimmed(); var.value=var.value.remove(QLatin1String("\n")); //lists with many elements have line breaks, remove them } else var.value=QLatin1String("unknown"); variables<(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(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(m_functionExpression); + QStringList functions; + for (Variable var : newFuncs) + functions << var.name.left(var.name.indexOf(QLatin1Char('('))); + qDebug() << functions; + 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< */ #ifndef _MAXIMAVARIABLEMODEL_H #define _MAXIMAVARIABLEMODEL_H #include "defaultvariablemodel.h" #include class MaximaSession; +class MaximaExpression; class MaximaVariableModel : public Cantor::DefaultVariableModel { Q_OBJECT public: static const QString inspectCommand; static const QString variableInspectCommand; 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; + MaximaExpression* m_variableExpression; + MaximaExpression* m_functionExpression; }; #endif /* _MAXIMAVARIABLEMODEL_H */ diff --git a/src/backends/maxima/testmaxima.cpp b/src/backends/maxima/testmaxima.cpp index e117e9c5..73876ceb 100644 --- a/src/backends/maxima/testmaxima.cpp +++ b/src/backends/maxima/testmaxima.cpp @@ -1,296 +1,297 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "testmaxima.h" #include "session.h" #include "backend.h" #include "expression.h" #include "result.h" #include "imageresult.h" #include "epsresult.h" #include "syntaxhelpobject.h" +#include "defaultvariablemodel.h" #include #include QString TestMaxima::backendName() { return QLatin1String("maxima"); } void TestMaxima::testSimpleCommand() { Cantor::Expression* e=evalExp( QLatin1String("2+2") ); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( cleanOutput( e->result()->toHtml() ), QLatin1String("4") ); } void TestMaxima::testMultilineCommand() { Cantor::Expression* e = evalExp( QLatin1String("2+2;3+3") ); QVERIFY(e != nullptr); QVERIFY(e->results().size() == 2); QString result = e->results().at(0)->toHtml(); QCOMPARE(result, QLatin1String("4")); result = e->results().at(1)->toHtml(); QCOMPARE(result, QLatin1String("6")); } //WARNING: for this test to work, Integration of Plots must be enabled //and CantorLib must be compiled with EPS-support void TestMaxima::testPlot() { if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull()) { QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle); } Cantor::Expression* e=evalExp( QLatin1String("plot2d(sin(x), [x, -10,10])") ); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); if(e->result()->type()!= Cantor::EpsResult::Type) { waitForSignal(e, SIGNAL(gotResult())); } #ifdef WITH_EPS QCOMPARE( e->result()->type(), (int)Cantor::EpsResult::Type ); #else QCOMPARE( e->result()->type(), (int)Cantor::ImageResult::Type ); #endif QVERIFY( !e->result()->data().isNull() ); QVERIFY( e->errorMessage().isNull() ); } void TestMaxima::testPlotWithAnotherTextResults() { if(QStandardPaths::findExecutable(QLatin1String("gnuplot")).isNull()) { QSKIP("gnuplot not found, maxima needs it for plotting", SkipSingle); } Cantor::Expression* e=evalExp( QLatin1String( "2*2; \n" "plot2d(sin(x), [x, -10,10]); \n" "4*4;" )); QVERIFY( e!=nullptr ); QVERIFY( e->errorMessage().isNull() ); QCOMPARE(e->results().size(), 3); QCOMPARE(e->results()[0]->toHtml(), QLatin1String("4")); #ifdef WITH_EPS QCOMPARE( e->results()[1]->type(), (int)Cantor::EpsResult::Type ); #else QCOMPARE( e->results()[1]->type(), (int)Cantor::ImageResult::Type ); #endif QVERIFY( !e->results()[1]->data().isNull() ); QCOMPARE(e->results()[2]->toHtml(), QLatin1String("16")); } void TestMaxima::testInvalidSyntax() { Cantor::Expression* e=evalExp( QLatin1String("2+2*(") ); QVERIFY( e!=nullptr ); QVERIFY( e->status()==Cantor::Expression::Error ); } void TestMaxima::testExprNumbering() { Cantor::Expression* e=evalExp( QLatin1String("kill(labels)") ); //first reset the labels e=evalExp( QLatin1String("2+2") ); QVERIFY( e!=nullptr ); int id=e->id(); QCOMPARE( id, 1 ); e=evalExp( QString::fromLatin1("%o%1+1" ).arg( id ) ); QVERIFY( e != nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( cleanOutput( e->result()->toHtml() ), QLatin1String( "5" ) ); } void TestMaxima::testCommandQueue() { //only wait for the last Expression to return, so the queue gets //actually filled Cantor::Expression* e1=session()->evaluateExpression(QLatin1String("0+1")); Cantor::Expression* e2=session()->evaluateExpression(QLatin1String("1+1")); Cantor::Expression* e3=evalExp(QLatin1String("1+2")); QVERIFY(e1!=nullptr); QVERIFY(e2!=nullptr); QVERIFY(e3!=nullptr); QVERIFY(e1->result()); QVERIFY(e2->result()); QVERIFY(e3->result()); QCOMPARE(cleanOutput(e1->result()->toHtml()), QLatin1String("1")); QCOMPARE(cleanOutput(e2->result()->toHtml()), QLatin1String("2")); QCOMPARE(cleanOutput(e3->result()->toHtml()), QLatin1String("3")); } void TestMaxima::testSimpleExpressionWithComment() { Cantor::Expression* e=evalExp(QLatin1String("/*this is a comment*/2+2")); QVERIFY(e!=nullptr); QVERIFY(e->result()!=nullptr); QCOMPARE(cleanOutput(e->result()->toHtml()), QLatin1String("4")); } void TestMaxima::testCommentExpression() { Cantor::Expression* e=evalExp(QLatin1String("/*this is a comment*/")); QVERIFY(e!=nullptr); QVERIFY(e->result()==nullptr||e->result()->toHtml().isEmpty()); } void TestMaxima::testNestedComment() { Cantor::Expression* e=evalExp(QLatin1String("/*/*this is still a comment*/*/2+2/*still/*a*/comment*//**/")); QVERIFY(e!=nullptr); QVERIFY(e->result()!=nullptr); QCOMPARE(cleanOutput(e->result()->toHtml()), QLatin1String("4")); } void TestMaxima::testUnmatchedComment() { Cantor::Expression* e=evalExp(QLatin1String("/*this comment doesn't end here!")); QVERIFY(e!=nullptr); QVERIFY(e->result()==nullptr); QVERIFY(e->status()==Cantor::Expression::Error); } void TestMaxima::testInvalidAssignment() { Cantor::Expression* e=evalExp(QLatin1String("0:a")); QVERIFY(e!=nullptr); //QVERIFY(e->result()==0); //QVERIFY(e->status()==Cantor::Expression::Error); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); //make sure we didn't screw up the session Cantor::Expression* e2=evalExp(QLatin1String("2+2")); QVERIFY(e2!=nullptr); QVERIFY(e2->result()!=nullptr); QCOMPARE(cleanOutput(e2->result()->toHtml()), QLatin1String("4")); } void TestMaxima::testInformationRequest() { Cantor::Expression* e=session()->evaluateExpression(QLatin1String("integrate(x^n,x)")); QVERIFY(e!=nullptr); waitForSignal(e, SIGNAL(needsAdditionalInformation(QString))); e->addInformation(QLatin1String("N")); waitForSignal(e, SIGNAL(statusChanged(Cantor::Expression::Status))); QVERIFY(e->result()!=nullptr); QCOMPARE(cleanOutput(e->result()->toHtml()), QLatin1String("x^(n+1)/(n+1)")); } void TestMaxima::testSyntaxHelp() { Cantor::SyntaxHelpObject* help = session()->syntaxHelpFor(QLatin1String("simplify_sum")); help->fetchSyntaxHelp(); waitForSignal(help, SIGNAL(done())); qWarning()<toHtml(); QVERIFY(help->toHtml().contains(QLatin1String("simplify_sum"))); } void TestMaxima::testHelpRequest() { //execute "??print" Cantor::Expression* e = session()->evaluateExpression(QLatin1String("??print")); QVERIFY(e != nullptr); //help result will be shown, but maxima still expects further input waitForSignal(e, SIGNAL(needsAdditionalInformation(QString))); QVERIFY(e->status() != Cantor::Expression::Done); //ask for help for the first flag of the print command e->addInformation(QLatin1String("0")); //no further input is required, we're done waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QVERIFY(e->status() == Cantor::Expression::Done); } void TestMaxima::testVariableModel() { QAbstractItemModel* model = session()->variableModel(); QVERIFY(model != nullptr); Cantor::Expression* e1=evalExp(QLatin1String("a: 15")); Cantor::Expression* e2=evalExp(QLatin1String("a: 15; b: \"Hello, world!\"")); Cantor::Expression* e3=evalExp(QLatin1String("l: [1,2,3]")); QVERIFY(e1!=nullptr); QVERIFY(e2!=nullptr); QVERIFY(e3!=nullptr); if(session()->status()==Cantor::Session::Running) waitForSignal(session(), SIGNAL(statusChanged(Cantor::Session::Status))); QCOMPARE(3, model->rowCount()); QVariant name = model->index(0,0).data(); QCOMPARE(name.toString(),QLatin1String("a")); QVariant value = model->index(0,1).data(); QCOMPARE(value.toString(),QLatin1String("15")); QVariant name1 = model->index(1,0).data(); QCOMPARE(name1.toString(),QLatin1String("b")); QVariant value1 = model->index(1,1).data(); QCOMPARE(value1.toString(),QLatin1String("\"Hello, world!\"")); QVariant name2 = model->index(2,0).data(); QCOMPARE(name2.toString(),QLatin1String("l")); QVariant value2 = model->index(2,1).data(); QCOMPARE(value2.toString(),QLatin1String("[1,2,3]")); } QTEST_MAIN( TestMaxima ) diff --git a/src/backends/octave/CMakeLists.txt b/src/backends/octave/CMakeLists.txt index 93feabf2..bc178d1e 100644 --- a/src/backends/octave/CMakeLists.txt +++ b/src/backends/octave/CMakeLists.txt @@ -1,43 +1,44 @@ set( OctaveBackend_SRCS octavebackend.cpp octavesession.cpp octaveexpression.cpp octaveextensions.cpp octavehighlighter.cpp octavekeywords.cpp octavecompletionobject.cpp octavesyntaxhelpobject.cpp + octavevariablemodel.cpp ) set (OCTAVE_SCRIPT_INSTALL_DIR ${KDE_INSTALL_DATADIR}/cantor/octavebackend) set (OCTAVE_SCRIPT_INSTALL_PATH ${CMAKE_INSTALL_PREFIX}/${OCTAVE_SCRIPT_INSTALL_DIR}/) configure_file (octave-backend-config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/octave-backend-config.h ) add_subdirectory(scripts) kconfig_add_kcfg_files(OctaveBackend_SRCS settings.kcfgc) install(FILES octavebackend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) ki18n_wrap_ui(OctaveBackend_SRCS settings.ui) add_backend(octavebackend ${OctaveBackend_SRCS}) target_link_libraries(cantor_octavebackend KF5::KIOCore KF5::ConfigCore KF5::ConfigGui KF5::SyntaxHighlighting ) if(BUILD_TESTING) add_executable( testoctave testoctave.cpp) add_test(testoctave testoctave) ecm_mark_as_test(testoctave) target_link_libraries( testoctave Qt5::Test cantorlibs cantortest ) endif(BUILD_TESTING) install(FILES cantor_octave.knsrc DESTINATION ${KDE_INSTALL_CONFDIR} ) diff --git a/src/backends/octave/octavebackend.cpp b/src/backends/octave/octavebackend.cpp index 8554ec5c..8076791c 100644 --- a/src/backends/octave/octavebackend.cpp +++ b/src/backends/octave/octavebackend.cpp @@ -1,98 +1,104 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "octavebackend.h" #include "octavesession.h" #include "octaveextensions.h" #include "settings.h" #include "cantor_macros.h" #include #include #include "ui_settings.h" OctaveBackend::OctaveBackend(QObject* parent, const QList& args): Backend(parent, args) { new OctaveHistoryExtension(this); new OctaveScriptExtension(this); new OctavePlotExtension(this); new OctaveLinearAlgebraExtension(this); new OctaveVariableManagementExtension(this); new OctavePackagingExtension(this); } QString OctaveBackend::id() const { return QLatin1String("octave"); } QString OctaveBackend::version() const { return QLatin1String("4.0 and 4.2"); } Cantor::Backend::Capabilities OctaveBackend::capabilities() const { - return SyntaxHighlighting | Completion | SyntaxHelp | VariableManagement; + Cantor::Backend::Capabilities cap= + SyntaxHighlighting| + Completion | + SyntaxHelp; + if (OctaveSettings::self()->variableManagement()) + cap |= VariableManagement; + return cap; } Cantor::Session* OctaveBackend::createSession() { return new OctaveSession(this); } bool OctaveBackend::requirementsFullfilled() const { return QFileInfo(OctaveSettings::path().toLocalFile()).isExecutable(); } QUrl OctaveBackend::helpUrl() const { const QUrl& localDoc = OctaveSettings::self()->localDoc(); if (!localDoc.isEmpty()) return localDoc; else return QUrl(i18nc("the url to the documentation of Octave, please check if there is a translated version (currently Czech and Japanese) and use the correct url", "http://www.gnu.org/software/octave/doc/interpreter/")); } QString OctaveBackend::description() const { return i18n("GNU Octave is a high-level language, primarily intended for numerical computations.
" "It provides a convenient command line interface for solving linear and nonlinear problems numerically, and for performing other numerical experiments using a language that is mostly compatible with Matlab."); } QWidget* OctaveBackend::settingsWidget(QWidget* parent) const { QWidget* widget = new QWidget(parent); Ui::OctaveSettingsBase ui; ui.setupUi(widget); return widget; } KConfigSkeleton* OctaveBackend::config() const { return OctaveSettings::self(); } K_PLUGIN_FACTORY_WITH_JSON(octavebackend, "octavebackend.json", registerPlugin();) #include "octavebackend.moc" diff --git a/src/backends/octave/octavebackend.kcfg b/src/backends/octave/octavebackend.kcfg index b8d7014a..da9b14d9 100644 --- a/src/backends/octave/octavebackend.kcfg +++ b/src/backends/octave/octavebackend.kcfg @@ -1,24 +1,28 @@ QStandardPaths QUrl::fromLocalFile(QStandardPaths::findExecutable( QLatin1String("octave-cli") )) true + + + true + diff --git a/src/backends/octave/octaveexpression.cpp b/src/backends/octave/octaveexpression.cpp index 0a85a035..78c22661 100644 --- a/src/backends/octave/octaveexpression.cpp +++ b/src/backends/octave/octaveexpression.cpp @@ -1,173 +1,151 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "octaveexpression.h" #include "octavesession.h" #include "defaultvariablemodel.h" #include "textresult.h" #include #include #include static const QLatin1String printCommand("cantor_print();"); OctaveExpression::OctaveExpression(Cantor::Session* session, bool internal): Expression(session, internal), m_plotPending(false), m_finished(false) { m_plotCommands << QLatin1String("plot") << QLatin1String("semilogx") << QLatin1String("semilogy") << QLatin1String("loglog") << QLatin1String("polar") << QLatin1String("contour") << QLatin1String("bar") << QLatin1String("stairs") << QLatin1String("errorbar") << QLatin1String("sombrero") << QLatin1String("hist") << QLatin1String("fplot") << QLatin1String("imshow") << QLatin1String("stem") << QLatin1String("stem3") << QLatin1String("scatter") << QLatin1String("pareto") << QLatin1String("rose") << QLatin1String("pie") << QLatin1String("quiver") << QLatin1String("compass") << QLatin1String("feather") << QLatin1String("pcolor") << QLatin1String("area") << QLatin1String("fill") << QLatin1String("comet") << QLatin1String("plotmatrix") /* 3d-plots */ << QLatin1String("plot3") << QLatin1String("mesh") << QLatin1String("meshc") << QLatin1String("meshz") << QLatin1String("surf") << QLatin1String("surfc") << QLatin1String("surfl") << QLatin1String("surfnorm") << QLatin1String("isosurface")<< QLatin1String("isonormals") << QLatin1String("isocaps") /* 3d-plots defined by a function */ << QLatin1String("ezplot3") << QLatin1String("ezmesh") << QLatin1String("ezmeshc") << QLatin1String("ezsurf") << QLatin1String("ezsurfc"); m_plotCommands << QLatin1String("cantor_plot2d") << QLatin1String("cantor_plot3d"); } void OctaveExpression::interrupt() { qDebug() << "interrupt"; setStatus(Interrupted); } void OctaveExpression::evaluate() { qDebug() << "evaluate"; QString cmd = command(); QStringList cmdWords = cmd.split(QRegExp(QLatin1String("\\b")), QString::SkipEmptyParts); if (!cmdWords.contains(QLatin1String("help")) && !cmdWords.contains(QLatin1String("completion_matches"))) { foreach (const QString& plotCmd, m_plotCommands) { if (cmdWords.contains(plotCmd)) { setPlotPending(true); qDebug() << "Executing a plot command"; break; } } } m_finished = false; session()->enqueueExpression(this); } QString OctaveExpression::internalCommand() { QString cmd = command(); if (m_plotPending && !cmd.contains(QLatin1String("cantor_plot")) && !cmd.contains(printCommand)) { if (!cmd.endsWith(QLatin1Char(';')) && !cmd.endsWith(QLatin1Char(','))) cmd += QLatin1Char(','); cmd += printCommand; } cmd.replace(QLatin1String(";\n"), QLatin1String(";")); cmd.replace(QLatin1Char('\n'), QLatin1Char(',')); cmd += QLatin1Char('\n'); return cmd; } void OctaveExpression::parseOutput(const QString& output) { qDebug() << "parseOutput: " << output; if (!output.trimmed().isEmpty()) { // TODO: what about help in comment? printf with '... help ...'? // This must be corrected. if (command().contains(QLatin1String("help"))) { setResult(new Cantor::HelpResult(output)); } else { setResult(new Cantor::TextResult(output)); } } - // TODO: remove this, then there is method for notify both Highlighter and variable model about new variable - foreach ( const QString& line, output.simplified().split(QLatin1Char('\n'), QString::SkipEmptyParts) ) - { - if ((output.contains(QLatin1Char('='))) && !(command().startsWith(QLatin1String("help("))) - && !(command().contains(QLatin1String("help "))) && !(command().contains(QLatin1String("type(")))) - { - qDebug() << line; - // Probably a new variable - QStringList parts = line.split(QLatin1Char('=')); - if (parts.size() >= 2) - { - Cantor::DefaultVariableModel* model = dynamic_cast(session()->variableModel()); - if (model) - { - const QString varname = parts.first().trimmed(); - if (varname != QLatin1String("__cantor_tmp__")) - model->addVariable(varname, parts.last().trimmed()); - } - } - } - } - m_finished = true; if (!m_plotPending) setStatus(Done); } void OctaveExpression::parseError(const QString& error) { setErrorMessage(error); setStatus(Error); } void OctaveExpression::parsePlotFile(const QString& file) { qDebug() << "parsePlotFile"; if (QFile::exists(file)) { qDebug() << "OctaveExpression::parsePlotFile: " << file; setResult(new OctavePlotResult(QUrl::fromLocalFile(file))); setPlotPending(false); if (m_finished) { setStatus(Done); } } } void OctaveExpression::setPlotPending(bool plot) { m_plotPending = plot; } diff --git a/src/backends/octave/octavehighlighter.cpp b/src/backends/octave/octavehighlighter.cpp index f23aeb0f..642439bd 100644 --- a/src/backends/octave/octavehighlighter.cpp +++ b/src/backends/octave/octavehighlighter.cpp @@ -1,95 +1,50 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "octavehighlighter.h" #include "octavekeywords.h" #include "result.h" #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, session) { addKeywords(OctaveKeywords::instance()->keywords()); addFunctions(OctaveKeywords::instance()->functions()); QStringList operators; operators << QLatin1String("+") << QLatin1String("-") << QLatin1String("*") << QLatin1String("/") << QLatin1String(".+") << QLatin1String(".-") << QLatin1String(".*") << QLatin1String("./") << QLatin1String("=") << QLatin1String("or") << QLatin1String("and") << QLatin1String("xor") << QLatin1String("not") << QLatin1String("||") << QLatin1String("&&") << QLatin1String("=="); addRules(operators, operatorFormat()); addRule(QRegExp(QLatin1String("\"[^\"]*\"")), stringFormat()); addRule(QRegExp(QLatin1String("'[^']*'")), stringFormat()); addRule(QRegExp(QLatin1String("#[^\n]*")), commentFormat()); addRule(QRegExp(QLatin1String("%[^\n]*")), commentFormat()); rehighlight(); } - -void OctaveHighlighter::updateVariables() -{ - 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(); - }); -} - -void OctaveHighlighter::sessionStatusChanged(Cantor::Session::Status status) -{ - if (status == Cantor::Session::Status::Disable) - for (const QString& variable: m_variables) - removeRule(variable); - rehighlight(); -} diff --git a/src/backends/octave/octavehighlighter.h b/src/backends/octave/octavehighlighter.h index 18494415..876be08e 100644 --- a/src/backends/octave/octavehighlighter.h +++ b/src/backends/octave/octavehighlighter.h @@ -1,48 +1,40 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OCTAVEHIGHLIGHTER_H #define OCTAVEHIGHLIGHTER_H #include "defaulthighlighter.h" #include namespace Cantor { class Expression; } class OctaveHighlighter : public Cantor::DefaultHighlighter { Q_OBJECT public: OctaveHighlighter(QObject* parent, Cantor::Session* session); ~OctaveHighlighter() override = default; - - public Q_SLOTS: - void updateVariables(); - void sessionStatusChanged(Cantor::Session::Status status); - - private: - Cantor::Session* m_session; - QStringList m_variables; }; #endif // OCTAVEHIGHLIGHTER_H diff --git a/src/backends/octave/octavesession.cpp b/src/backends/octave/octavesession.cpp index b9ccdfa1..3dd1824b 100644 --- a/src/backends/octave/octavesession.cpp +++ b/src/backends/octave/octavesession.cpp @@ -1,388 +1,369 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "octavesession.h" #include "octaveexpression.h" #include "octavecompletionobject.h" #include "octavesyntaxhelpobject.h" #include "octavehighlighter.h" #include "result.h" #include "textresult.h" #include "settings.h" #include "octave-backend-config.h" #include "octavehighlighter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif -#include +#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; } void OctaveSession::login() { qDebug() << "login"; emit loginStarted(); m_process = new KProcess ( this ); QStringList args; args << QLatin1String("--silent"); args << QLatin1String("--interactive"); args << QLatin1String("--persist"); // Setting prompt and subprompt args << QLatin1String("--eval"); args << QLatin1String("PS1('CANTOR_OCTAVE_BACKEND_PROMPT:\\#> ');"); args << QLatin1String("--eval"); args << QLatin1String("PS2('CANTOR_OCTAVE_BACKEND_SUBPROMPT:\\#> ');"); // Add the cantor script directory to search path args << QLatin1String("--eval"); args << QString::fromLatin1("addpath %1;").arg(octaveScriptInstallDir); // Print the temp dir, used for plot files args << QLatin1String("--eval"); args << QLatin1String("printf('%s\\n', ['____TMP_DIR____ = ' tempdir]);"); // Do not show extra text in help commands args << QLatin1String("--eval"); args << QLatin1String("suppress_verbose_help_message(1);"); if (OctaveSettings::integratePlots()) { // Do not show the popup when plotting, rather only print to a file args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"off\");"); args << QLatin1String("--eval"); args << QLatin1String("graphics_toolkit gnuplot;"); } else { args << QLatin1String("--eval"); args << QLatin1String("set (0, \"defaultfigurevisible\",\"on\");"); } if (OctaveSettings::integratePlots()) { m_watch = new KDirWatch(this); m_watch->setObjectName(QLatin1String("OctaveDirWatch")); connect (m_watch, SIGNAL(dirty(QString)), SLOT(plotFileChanged(QString)) ); } m_process->setProgram ( OctaveSettings::path().toLocalFile(), args ); qDebug() << "starting " << m_process->program(); m_process->setOutputChannelMode ( KProcess::SeparateChannels ); m_process->start(); m_process->waitForStarted(); // Got tmp dir bool loginFinished = false; QString input; while (!loginFinished) { m_process->waitForReadyRead(); input += QString::fromLatin1(m_process->readAllStandardOutput()); qDebug() << "login input: " << input; if (input.contains(QLatin1String("____TMP_DIR____"))) { m_tempDir = input; m_tempDir.remove(0,18); m_tempDir.chop(1); // isolate the tempDir's location qDebug() << "Got temporary file dir:" << m_tempDir; if (m_watch) { m_watch->addDir(m_tempDir, KDirWatch::WatchFiles); } loginFinished = true; } } connect ( m_process, SIGNAL (readyReadStandardOutput()), SLOT (readOutput()) ); connect ( m_process, SIGNAL (readyReadStandardError()), SLOT (readError()) ); connect ( m_process, SIGNAL (error(QProcess::ProcessError)), SLOT (processError()) ); if(!OctaveSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = OctaveSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, OctaveExpression::DeleteOnFinish, true); + updateVariables(); } changeStatus(Cantor::Session::Done); emit loginDone(); qDebug()<<"login done"; } void OctaveSession::logout() { qDebug()<<"logout"; if(!m_process) return; disconnect(m_process, nullptr, this, nullptr); // if(status()==Cantor::Session::Running) // TODO: terminate the running expressions first m_process->write("exit\n"); qDebug()<<"waiting for octave to finish"; m_process->waitForFinished(); qDebug()<<"octave exit finished"; if(m_process->state() != QProcess::NotRunning) { m_process->kill(); qDebug()<<"octave still running, process kill enforced"; } expressionQueue().clear(); delete m_process; m_process = nullptr; m_tempDir.clear(); m_output.clear(); m_previousPromptNumber = 1; - m_variableModel->clearVariables(); + variableModel()->clearVariables(); changeStatus(Status::Disable); qDebug()<<"logout done"; } void OctaveSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); // Cleanup inner state and call octave prompt printing // If we move this code for interruption to Session, we need add function for // cleaning before setting Done status m_output.clear(); m_process->write("\n"); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } void OctaveSession::processError() { qDebug() << "processError"; emit error(m_process->errorString()); } Cantor::Expression* OctaveSession::evaluateExpression ( const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior, bool internal ) { qDebug() << "evaluating: " << command; OctaveExpression* expression = new OctaveExpression ( this, internal); expression->setCommand ( command ); expression->setFinishingBehavior ( finishingBehavior ); expression->evaluate(); return expression; } void OctaveSession::runFirstExpression() { OctaveExpression* expression = static_cast(expressionQueue().first()); connect(expression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); QString command = expression->internalCommand(); expression->setStatus(Cantor::Expression::Computing); if (isDoNothingCommand(command)) expression->setStatus(Cantor::Expression::Done); else { m_process->write ( command.toLocal8Bit() ); } } void OctaveSession::readError() { qDebug() << "readError"; QString error = QString::fromLocal8Bit(m_process->readAllStandardError()); if (!expressionQueue().isEmpty() && !error.isEmpty()) { OctaveExpression* const exp = static_cast(expressionQueue().first()); if (m_syntaxError) { m_syntaxError = false; exp->parseError(i18n("Syntax Error")); } else exp->parseError(error); m_output.clear(); } } void OctaveSession::readOutput() { qDebug() << "readOutput"; while (m_process->bytesAvailable() > 0) { QString line = QString::fromLocal8Bit(m_process->readLine()); qDebug()<<"start parsing " << " " << line; if (line.contains(m_prompt)) { const int promptNumber = m_prompt.cap(1).toInt(); if (!expressionQueue().isEmpty()) { const QString& command = expressionQueue().first()->command(); if (m_previousPromptNumber + 1 == promptNumber || isSpecialOctaveCommand(command)) { if (!expressionQueue().isEmpty()) - { - if (command.contains(QLatin1String(" = ")) || command.contains(QLatin1String("clear"))) - { - emit variablesChanged(); - } static_cast(expressionQueue().first())->parseOutput(m_output); - } } else { // Error command don't increase octave prompt number (usually, but not always) readError(); } } m_previousPromptNumber = promptNumber; m_output.clear(); } else if (line.contains(m_subprompt) && m_subprompt.cap(1).toInt() == m_previousPromptNumber) { // User don't write finished octave statement (for example, write 'a = [1,2, ' only), so // octave print subprompt and waits input finish. m_syntaxError = true; qDebug() << "subprompt catch"; m_process->write(")]'\"\n"); // forse exit from subprompt m_output.clear(); } else m_output += line; } } void OctaveSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { qDebug() << "currentExpressionStatusChanged"; switch (status) { case Cantor::Expression::Done: case Cantor::Expression::Error: - expressionQueue().removeFirst(); - if (expressionQueue().isEmpty()) - changeStatus(Done); - else - runFirstExpression(); + finishFirstExpression(); break; + default: break; } } void OctaveSession::plotFileChanged(const QString& filename) { if (!QFile::exists(filename) || !filename.split(QLatin1Char('/')).last().contains(QLatin1String("c-ob-"))) { return; } if (!expressionQueue().isEmpty()) { static_cast(expressionQueue().first())->parsePlotFile(filename); } } Cantor::CompletionObject* OctaveSession::completionFor ( const QString& cmd, int index ) { return new OctaveCompletionObject ( cmd, index, this ); } Cantor::SyntaxHelpObject* OctaveSession::syntaxHelpFor ( const QString& cmd ) { return new OctaveSyntaxHelpObject ( cmd, this ); } QSyntaxHighlighter* OctaveSession::syntaxHighlighter ( QObject* parent ) { - OctaveHighlighter* highlighter = new OctaveHighlighter ( parent, this ); - - connect ( this, SIGNAL(variablesChanged()), highlighter, SLOT(updateVariables()) ); - connect ( this, SIGNAL(statusChanged(Cantor::Session::Status)), highlighter, SLOT(sessionStatusChanged(Cantor::Session::Status)) ); - - return highlighter; -} - -QAbstractItemModel* OctaveSession::variableModel() -{ - return m_variableModel; + return new OctaveHighlighter ( parent, this ); } - void OctaveSession::runSpecificCommands() { m_process->write("figure(1,'visible','off')"); } bool OctaveSession::isDoNothingCommand(const QString& command) { return PROMPT_UNCHANGEABLE_COMMAND.exactMatch(command) || command.isEmpty(); } bool OctaveSession::isSpecialOctaveCommand(const QString& command) { return command.contains(QLatin1String("completion_matches")); } diff --git a/src/backends/octave/octavesession.h b/src/backends/octave/octavesession.h index 52ab77a9..a5049824 100644 --- a/src/backends/octave/octavesession.h +++ b/src/backends/octave/octavesession.h @@ -1,89 +1,83 @@ /* Copyright (C) 2010 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OCTAVESESSION_H #define OCTAVESESSION_H #include #include #include #include #include namespace Cantor { class DefaultVariableModel; } class KDirWatch; class OctaveExpression; class KProcess; class OctaveSession : public Cantor::Session { Q_OBJECT public: explicit OctaveSession(Cantor::Backend* backend); ~OctaveSession() override = default; void interrupt() override; Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior finishingBehavior = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; void logout() override; void login() override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& cmd) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; - QAbstractItemModel* variableModel() override; void runFirstExpression() override; - Q_SIGNALS: - void variablesChanged(); - private: const static QRegExp PROMPT_UNCHANGEABLE_COMMAND; private: KProcess* m_process; QTextStream m_stream; QRegExp m_prompt; QRegExp m_subprompt; int m_previousPromptNumber; KDirWatch* m_watch; QString m_tempDir; bool m_syntaxError; QString m_output; - Cantor::DefaultVariableModel* m_variableModel; - private: void readFromOctave(QByteArray data); bool isDoNothingCommand(const QString& command); bool isSpecialOctaveCommand(const QString& command); private Q_SLOTS: void readOutput(); void readError(); void currentExpressionStatusChanged(Cantor::Expression::Status status); void processError(); void plotFileChanged(const QString& filename); void runSpecificCommands(); }; #endif // OCTAVESESSION_H diff --git a/src/backends/octave/octavevariablemodel.cpp b/src/backends/octave/octavevariablemodel.cpp new file mode 100644 index 00000000..69e9243a --- /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/python2/testpython2.h b/src/backends/octave/octavevariablemodel.h similarity index 60% copy from src/backends/python2/testpython2.h copy to src/backends/octave/octavevariablemodel.h index ed068c88..e0549557 100644 --- a/src/backends/python2/testpython2.h +++ b/src/backends/octave/octavevariablemodel.h @@ -1,40 +1,43 @@ /* 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) 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/settings.ui b/src/backends/octave/settings.ui index e4fa0e06..b558ff11 100644 --- a/src/backends/octave/settings.ui +++ b/src/backends/octave/settings.ui @@ -1,85 +1,95 @@ OctaveSettingsBase 0 0 400 300 Path to Octave: Path to local documentation: Integrate Plots in Worksheet + + + + Let Cantor follow the creation/destruction of variables + + + Enable Variable Management + + + Scripts to autorun Qt::Vertical 20 40 KUrlRequester QFrame
kurlrequester.h
diff --git a/src/backends/octave/testoctave.cpp b/src/backends/octave/testoctave.cpp index 54c09a7a..45691435 100644 --- a/src/backends/octave/testoctave.cpp +++ b/src/backends/octave/testoctave.cpp @@ -1,178 +1,221 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "testoctave.h" #include "session.h" #include "backend.h" #include "expression.h" #include "result.h" #include "imageresult.h" #include "textresult.h" #include "epsresult.h" #include "completionobject.h" +#include "defaultvariablemodel.h" #include "octaveexpression.h" #include QString TestOctave::backendName() { return QLatin1String("octave"); } void TestOctave::testSimpleCommand() { Cantor::Expression* e=evalExp( QLatin1String("2+2") ); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( cleanOutput( e->result()->toHtml() ), QLatin1String("ans = 4") ); } void TestOctave::testMultilineCommand() { Cantor::Expression* e=evalExp( QLatin1String("a = 2+2, b = 3+3") ); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QString result=e->result()->toHtml(); QCOMPARE( cleanOutput(result ), QLatin1String("a = 4\nb = 6") ); } void TestOctave::testCommandQueue() { Cantor::Expression* e1=session()->evaluateExpression(QLatin1String("0+1")); Cantor::Expression* e2=session()->evaluateExpression(QLatin1String("1+1")); Cantor::Expression* e3=evalExp(QLatin1String("1+2")); QVERIFY(e1!=nullptr); QVERIFY(e2!=nullptr); QVERIFY(e3!=nullptr); QVERIFY(e1->result()); QVERIFY(e2->result()); QVERIFY(e3->result()); QCOMPARE(cleanOutput(e1->result()->toHtml()), QLatin1String("ans = 1")); QCOMPARE(cleanOutput(e2->result()->toHtml()), QLatin1String("ans = 2")); QCOMPARE(cleanOutput(e3->result()->toHtml()), QLatin1String("ans = 3")); } void TestOctave::testVariableDefinition() { Cantor::Expression* e = evalExp(QLatin1String("testvar = 1")); QVERIFY(e != nullptr); QVERIFY(e->result() != nullptr); QCOMPARE(cleanOutput(e->result()->toHtml()), QLatin1String("testvar = 1")); } void TestOctave::testMatrixDefinition() { Cantor::Expression* e = evalExp(QLatin1String( "M = [1, 2, 3;"\ " 4, 5, 6;"\ " 7, 8, 9;]" )); QVERIFY(e != nullptr); QVERIFY(e->result() != nullptr); QCOMPARE(e->result()->type(), (int) Cantor::TextResult::Type); Cantor::TextResult* result = static_cast(e->result()); QCOMPARE(result->plain(), QLatin1String( "M =\n"\ "\n" " 1 2 3\n"\ " 4 5 6\n"\ " 7 8 9" )); } void TestOctave::testSimpleExpressionWithComment() { Cantor::Expression* e = evalExp(QLatin1String("s = 1234 #This is comment")); QVERIFY(e != nullptr); QVERIFY(e->result() != nullptr); QCOMPARE(cleanOutput(e->result()->toHtml()), QLatin1String("s = 1234")); } void TestOctave::testCommentExpression() { // https://bugs.kde.org/show_bug.cgi?id=401893 QSKIP("Skip, until we fix the bug #401893"); Cantor::Expression* e = evalExp(QLatin1String("#Only comment")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Status::Done); QCOMPARE(e->results().size(), 0); } void TestOctave::testCompletion() { QSKIP("Skip, until solve strange fail of this test with anomaly output (double prompt with missing command)"); Cantor::CompletionObject* help = session()->completionFor(QLatin1String("as"), 2); waitForSignal(help, SIGNAL(fetchingDone())); // Checks some completions for this request (but not all) // This correct for Octave 4.2.2 at least (and another versions, I think) const QStringList& completions = help->completions(); qDebug() << completions; QVERIFY(completions.contains(QLatin1String("asin"))); QVERIFY(completions.contains(QLatin1String("asctime"))); QVERIFY(completions.contains(QLatin1String("asec"))); 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 = 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);") ); int cnt=0; //give some time to create the image, but at most 5sec while(e->result()==nullptr||e->result()->type()!=OctavePlotResult::Type ) { QTest::qWait(250); cnt+=250; if(cnt>5000) break; } QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QCOMPARE( e->result()->type(), (int) OctavePlotResult::Type ); QVERIFY( !e->result()->data().isNull() ); } void TestOctave::testInvalidSyntax() { Cantor::Expression* e=evalExp( QLatin1String("2+2*+.") ); QVERIFY( e!=nullptr ); QCOMPARE( e->status(), Cantor::Expression::Error ); } QTEST_MAIN( TestOctave ) diff --git a/src/backends/octave/testoctave.h b/src/backends/octave/testoctave.h index eb3351e1..7a2d9234 100644 --- a/src/backends/octave/testoctave.h +++ b/src/backends/octave/testoctave.h @@ -1,64 +1,68 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _TESTOCTAVE_H #define _TESTOCTAVE_H #include "backendtest.h" /** This class test some of the basic functions of the Octave backend The different tests represent some general expressions, as well as expressions, that are known to have caused problems in earlier versions **/ class TestOctave : public BackendTest { Q_OBJECT private Q_SLOTS: //tests evaluating a simple command void testSimpleCommand(); //tests a command, containing more than 1 line void testMultilineCommand(); //tests if the command queue works correcly void testCommandQueue(); void testVariableDefinition(); void testMatrixDefinition(); //some tests to see if comments are working correctly void testSimpleExpressionWithComment(); void testCommentExpression(); //tests a syntax error (not closing bracket) void testInvalidSyntax(); void testCompletion(); + //tests variable model + void testVariablesCreatingFromCode(); + void testVariableCleanupAfterRestart(); + //tests doing a plot void testPlot(); private: QString backendName() override; }; #endif /* _TESTOCTAVE_H */ diff --git a/src/backends/python/CMakeLists.txt b/src/backends/python/CMakeLists.txt index 2d10bf67..773d9b07 100644 --- a/src/backends/python/CMakeLists.txt +++ b/src/backends/python/CMakeLists.txt @@ -1,28 +1,29 @@ set( PythonBackend_SRCS pythonbackend.cpp pythonsession.cpp pythonexpression.cpp pythonkeywords.cpp + pythonvariablemodel.cpp pythonhighlighter.cpp pythoncompletionobject.cpp pythonextensions.cpp ) qt5_add_resources(PythonBackend_RSCS python.qrc) ki18n_wrap_ui(PythonBackend_SRCS settings.ui) add_library(cantor_pythonbackend SHARED ${PythonBackend_SRCS} ${PythonBackend_RSCS}) generate_export_header(cantor_pythonbackend) target_link_libraries(cantor_pythonbackend cantorlibs KF5::KIOCore KF5::ConfigCore KF5::ConfigGui KF5::SyntaxHighlighting Qt5::DBus ) install(TARGETS cantor_pythonbackend DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) #install(DIRECTORY . DESTINATION ${KDE_INSTALL_DATADIR}/cantor/pythonbackend FILES_MATCHING PATTERN "*.py") diff --git a/src/backends/python/pythonbackend.cpp b/src/backends/python/pythonbackend.cpp index 5191ee63..18dce4f7 100644 --- a/src/backends/python/pythonbackend.cpp +++ b/src/backends/python/pythonbackend.cpp @@ -1,62 +1,52 @@ /* 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) 2012 Filipe Saraiva */ #include "pythonbackend.h" #include "pythonsession.h" #include "pythonextensions.h" #include "ui_settings.h" #include #include PythonBackend::PythonBackend(QObject* parent, const QList& args) : Cantor::Backend(parent, args) { qDebug()<<"Creating PythonBackend"; new PythonLinearAlgebraExtension(this); new PythonPackagingExtension(this); new PythonPlotExtension(this); new PythonScriptExtension(this); new PythonVariableManagementExtension(this); } PythonBackend::~PythonBackend() { 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); Ui::PythonSettingsBase s; s.setupUi(widget); return widget; } diff --git a/src/backends/python/pythonbackend.h b/src/backends/python/pythonbackend.h index 16c10fd8..8eb2679e 100644 --- a/src/backends/python/pythonbackend.h +++ b/src/backends/python/pythonbackend.h @@ -1,41 +1,41 @@ /* 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) 2012 Filipe Saraiva */ #ifndef _PYTHONBACKEND_H #define _PYTHONBACKEND_H #include "backend.h" #include class CANTOR_PYTHONBACKEND_EXPORT PythonBackend : public Cantor::Backend { Q_OBJECT public: 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; }; #endif /* _PYTHONBACKEND_H */ diff --git a/src/backends/python/pythoncompletionobject.cpp b/src/backends/python/pythoncompletionobject.cpp index 4e4bd099..eba71ad4 100644 --- a/src/backends/python/pythoncompletionobject.cpp +++ b/src/backends/python/pythoncompletionobject.cpp @@ -1,169 +1,170 @@ /* 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) 2013 Filipe Saraiva */ #include "pythoncompletionobject.h" #include #include "result.h" #include "pythonsession.h" #include "pythonkeywords.h" PythonCompletionObject::PythonCompletionObject(const QString& command, int index, PythonSession* session) : Cantor::CompletionObject(session), m_expression(nullptr) { setLine(command, index); } void PythonCompletionObject::fetchCompletions() { if (session()->status() == Cantor::Session::Disable) { QStringList allCompletions; allCompletions << PythonKeywords::instance()->variables(); allCompletions << PythonKeywords::instance()->functions(); allCompletions << PythonKeywords::instance()->keywords(); setCompletions(allCompletions); emit fetchingDone(); } else { if (m_expression) return; qDebug() << "run fetchCompletions"; const QString& expr = QString::fromLatin1( "from __main__ import __dict__;" "from rlcompleter import Completer;" "print('|'.join(Completer(__dict__).global_matches('%1')+Completer(__dict__).attr_matches('%1')))" ).arg(command()); m_expression = session()->evaluateExpression(expr, Cantor::Expression::FinishingBehavior::DoNotDelete, true); // TODO: Python exec the expression before connect, so manualy run handler. Uncomment the connection after removing DBus // connect(m_expression, &Cantor::Expression::statusChanged, this, &PythonCompletionObject::extractCompletions); extractCompletions(m_expression->status()); } } void PythonCompletionObject::fetchIdentifierType() { if (session()->status() == Cantor::Session::Disable) { if (qBinaryFind(PythonKeywords::instance()->functions().begin(), PythonKeywords::instance()->functions().end(), identifier()) != PythonKeywords::instance()->functions().end()) emit fetchingTypeDone(FunctionType); else if (qBinaryFind(PythonKeywords::instance()->keywords().begin(), PythonKeywords::instance()->keywords().end(), identifier()) != PythonKeywords::instance()->keywords().end()) emit fetchingTypeDone(KeywordType); else emit fetchingTypeDone(VariableType); } else { if (m_expression) return; const QString& expr = QString::fromLatin1("callable(%1)").arg(identifier()); m_expression = session()->evaluateExpression(expr, Cantor::Expression::FinishingBehavior::DoNotDelete, true); // TODO: Python exec the expression before connect, so manualy run handler. Uncomment the connection after removing DBus // connect(m_expression, &Cantor::Expression::statusChanged, this, &PythonCompletionObject::extractIdentifierType); extractIdentifierType(m_expression->status()); } } void PythonCompletionObject::extractCompletions(Cantor::Expression::Status status) { if (!m_expression) return; + switch(status) { case Cantor::Expression::Error: qDebug() << "Error with PythonCompletionObject" << (m_expression->result() ? m_expression->result()->toHtml() : QLatin1String("extractCompletions")); break; case Cantor::Expression::Interrupted: qDebug() << "PythonCompletionObject was interrupted"; break; case Cantor::Expression::Done: if (m_expression->result()) setCompletions(m_expression->result()->toHtml().remove(QLatin1Char('(')).split(QLatin1Char('|'))); break; default: return; } m_expression->deleteLater(); m_expression = nullptr; emit fetchingDone(); } void PythonCompletionObject::extractIdentifierType(Cantor::Expression::Status status) { if (!m_expression) return; switch(status) { case Cantor::Expression::Error: if (m_expression->errorMessage().contains(QLatin1String("SyntaxError: invalid syntax"))) emit fetchingTypeDone(KeywordType); else qDebug() << "Error with PythonCompletionObject" << (m_expression->result() ? m_expression->result()->toHtml() : QLatin1String("extractIdentifierType")); break; case Cantor::Expression::Interrupted: qDebug() << "PythonCompletionObject was interrupted"; break; case Cantor::Expression::Done: if (m_expression->result()) { if (m_expression->result()) { if (m_expression->result()->toHtml() == QLatin1String("True")) emit fetchingTypeDone(FunctionType); else emit fetchingTypeDone(VariableType); } } break; default: return; } m_expression->deleteLater(); m_expression = nullptr; } bool PythonCompletionObject::mayIdentifierContain(QChar c) const { return c.isLetter() || c.isDigit() || c == QLatin1Char('_') || c == QLatin1Char('%') || c == QLatin1Char('$') || c == QLatin1Char('.'); } bool PythonCompletionObject::mayIdentifierBeginWith(QChar c) const { return c.isLetter() || c == QLatin1Char('_') || c == QLatin1Char('%') || c == QLatin1Char('$'); } diff --git a/src/backends/python/pythonhighlighter.cpp b/src/backends/python/pythonhighlighter.cpp index 4d17d002..55494a5c 100644 --- a/src/backends/python/pythonhighlighter.cpp +++ b/src/backends/python/pythonhighlighter.cpp @@ -1,162 +1,145 @@ /* 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) 2013 Filipe Saraiva */ #include "pythonhighlighter.h" #include "pythonkeywords.h" +#include "pythonsession.h" #include #include -PythonHighlighter::PythonHighlighter(QObject* parent, const int pythonVersion) : Cantor::DefaultHighlighter(parent) +PythonHighlighter::PythonHighlighter(QObject* parent, PythonSession* session, const int pythonVersion) : Cantor::DefaultHighlighter(parent, session) { qDebug() << "PythonHighlighter constructor"; addRule(QRegExp(QLatin1String("\\b\\w+(?=\\()")), functionFormat()); //Code highlighting the different keywords addKeywords(PythonKeywords::instance()->keywords()); addFunctions(PythonKeywords::instance()->functions()); addVariables(PythonKeywords::instance()->variables()); if (pythonVersion == 2) { removeRule(QLatin1String("print")); addRule(QLatin1String("print"), keywordFormat()); } } void PythonHighlighter::highlightBlock(const QString &text) { if (skipHighlighting(text)) { return; } // Do some backend independent highlighting (brackets etc.) DefaultHighlighter::highlightBlock(text); const int IN_MULTILINE_COMMENT = 1; const int IN_SMALL_QUOTE_STRING = 2; const int IN_SINGLE_QUOTE_STRING = 4; const int IN_TRIPLE_QUOTE_STRING = 8; QRegExp multiLineCommentStartEnd(QLatin1String("'''")); QRegExp smallQuoteStartEnd(QLatin1String("'")); QRegExp singleQuoteStringStartEnd(QLatin1String("\"")); QRegExp tripleQuoteStringStartEnd(QLatin1String("\"\"\"")); QRegExp singleLineCommentStart(QLatin1String("#")); int state = previousBlockState(); if (state == -1) { state = 0; } QList flags = { IN_TRIPLE_QUOTE_STRING, IN_SINGLE_QUOTE_STRING, IN_SMALL_QUOTE_STRING, IN_MULTILINE_COMMENT }; QList regexps = { tripleQuoteStringStartEnd, singleQuoteStringStartEnd, smallQuoteStartEnd, multiLineCommentStartEnd }; QList formats = { stringFormat(), stringFormat(), stringFormat(), commentFormat() }; int pos = 0; while (pos < text.length()) { // Trying to close current environments bool triggered = false; for (int i = 0; i < flags.size() && !triggered; i++) { int flag = flags[i]; QRegExp ®exp = regexps[i]; QTextCharFormat &format = formats[i]; if (state & flag) { int new_pos = regexp.indexIn(text, pos); int length; if (new_pos == -1) { length = text.length() - pos; } else { length = new_pos - pos + regexp.matchedLength(); state -= flag; } setFormat(pos, length, format); pos = pos + length; triggered = true; } } if (triggered) { continue; } QRegExp *minRegexp = nullptr; int minPos = INT_MAX; int minIdx = -1; for (int i = 0; i < regexps.size(); i++) { QRegExp ®exp = regexps[i]; int newPos = regexp.indexIn(text, pos); if (newPos != -1) { minPos = qMin(minPos, newPos); minRegexp = ®exp; minIdx = i; } } int singleLineCommentStartPos = singleLineCommentStart.indexIn(text, pos); if (singleLineCommentStartPos != -1 && singleLineCommentStartPos < minPos) { setFormat(pos, text.length() - pos, commentFormat()); break; } else if (minRegexp) { state += flags[minIdx]; pos = minPos + minRegexp->matchedLength(); setFormat(minPos, minRegexp->matchedLength(), formats[minIdx]); } else { break; } } setCurrentBlockState(state); } - -void PythonHighlighter::updateHighlight() -{ - addVariables(m_variables); - rehighlight(); -} - -void PythonHighlighter::addVariable(const QString variable) -{ - m_variables << variable; -} - -void PythonHighlighter::clearVariables() -{ - removeRules(m_variables); - m_variables.clear(); - rehighlight(); -} diff --git a/src/backends/python/pythonhighlighter.h b/src/backends/python/pythonhighlighter.h index 56d86d76..8c6009db 100644 --- a/src/backends/python/pythonhighlighter.h +++ b/src/backends/python/pythonhighlighter.h @@ -1,49 +1,43 @@ /* 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) 2013 Filipe Saraiva */ #ifndef _PYTHONHIGHLIGHTER_H #define _PYTHONHIGHLIGHTER_H #include "defaulthighlighter.h" +class PythonSession; class PythonHighlighter : public Cantor::DefaultHighlighter { Q_OBJECT public: - explicit PythonHighlighter(QObject* parent, const int pythonVersion); + explicit PythonHighlighter(QObject* parent, PythonSession* session, const int pythonVersion); ~PythonHighlighter() override = default; - public Q_SLOTS: - void updateHighlight(); - void addVariable(const QString variable); - void clearVariables(); - protected: void highlightBlock(const QString& text) override; private: QRegExp commentStartExpression; QRegExp commentEndExpression; - QStringList m_variables; - }; #endif /* _PYTHONHIGHLIGHTER_H */ diff --git a/src/backends/python/pythonserver.cpp b/src/backends/python/pythonserver.cpp index 8e150871..fc7af615 100644 --- a/src/backends/python/pythonserver.cpp +++ b/src/backends/python/pythonserver.cpp @@ -1,147 +1,205 @@ /* 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) 2015 Minh Ngo */ #include "pythonserver.h" #include PythonServer::PythonServer(QObject* parent) : QObject(parent), m_pModule(nullptr) { } namespace { QString pyObjectToQString(PyObject* obj) { #if PY_MAJOR_VERSION == 3 return QString::fromUtf8(PyUnicode_AsUTF8(obj)); #elif PY_MAJOR_VERSION == 2 return QString::fromLocal8Bit(PyString_AsString(obj)); #else #warning Unknown Python version #endif } } void PythonServer::login() { Py_InspectFlag = 1; Py_Initialize(); m_pModule = PyImport_AddModule("__main__"); filePath = QStringLiteral("python_cantor_worksheet"); } void PythonServer::runPythonCommand(const QString& command) const { PyObject* py_dict = PyModule_GetDict(m_pModule); const char* prepareCommand = "import sys;\n"\ "class CatchOutPythonBackend:\n"\ " def __init__(self):\n"\ " self.value = ''\n"\ " def write(self, txt):\n"\ " self.value += txt\n"\ "outputPythonBackend = CatchOutPythonBackend()\n"\ "errorPythonBackend = CatchOutPythonBackend()\n"\ "sys.stdout = outputPythonBackend\n"\ "sys.stderr = errorPythonBackend\n"; PyRun_SimpleString(prepareCommand); #if PY_MAJOR_VERSION == 3 PyObject* compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input); // There are two reasons for the error: // 1) This code is not single expression, so we can't compile this with flag Py_single_input // 2) There are errors in the code if (PyErr_Occurred()) { PyErr_Clear(); // Try to recompile code as sequence of expressions compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input); if (PyErr_Occurred()) { // We now know, that we have a syntax error, so print the traceback and exit PyErr_PrintEx(0); return; } } PyEval_EvalCode(compile, py_dict, py_dict); #elif PY_MAJOR_VERSION == 2 // Python 2.X don't check, that input string contains only one expression. // So for checking this, we compile string as file and as single expression and compare bytecode // FIXME? PyObject* codeFile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input); if (PyErr_Occurred()) { PyErr_PrintEx(0); return; } PyObject* codeSingle = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input); if (PyErr_Occurred()) { // We have error with Py_single_input, but haven't error with Py_file_input // So, the code can't be compiled as singel input -> use file input right away PyErr_Clear(); PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict); } else { PyObject* bytecode1 = ((PyCodeObject*)codeSingle)->co_code; PyObject* bytecode2 = ((PyCodeObject*)codeFile)->co_code; if (PyObject_Length(bytecode1) >= PyObject_Length(bytecode2)) { PyEval_EvalCode((PyCodeObject*)codeSingle, py_dict, py_dict); } else { PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict); } } #else #warning Unknown Python version #endif if (PyErr_Occurred()) PyErr_PrintEx(0); } QString PythonServer::getError() const { PyObject *errorPython = PyObject_GetAttrString(m_pModule, "errorPythonBackend"); PyObject *error = PyObject_GetAttrString(errorPython, "value"); return pyObjectToQString(error); } QString PythonServer::getOutput() const { PyObject *outputPython = PyObject_GetAttrString(m_pModule, "outputPythonBackend"); PyObject *output = PyObject_GetAttrString(outputPython, "value"); return pyObjectToQString(output); } void PythonServer::setFilePath(const QString& path) { this->filePath = path; 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/pythonserver.h b/src/backends/python/pythonserver.h index b4cd0f49..a36c2ee5 100644 --- a/src/backends/python/pythonserver.h +++ b/src/backends/python/pythonserver.h @@ -1,47 +1,48 @@ /* 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) 2015 Minh Ngo */ #ifndef _PYTHONSERVER_H #define _PYTHONSERVER_H #include #include struct _object; using PyObject = _object; class PythonServer : public QObject { Q_OBJECT public: explicit PythonServer(QObject* parent = nullptr); 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; + Q_SCRIPTABLE QString variables(bool parseValue) const; private: PyObject* m_pModule; QString filePath; }; #endif diff --git a/src/backends/python/pythonsession.cpp b/src/backends/python/pythonsession.cpp index f0db5e25..cdb3e89a 100644 --- a/src/backends/python/pythonsession.cpp +++ b/src/backends/python/pythonsession.cpp @@ -1,407 +1,361 @@ /* 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) 2012 Filipe Saraiva Copyright (C) 2015 Minh Ngo */ #include "pythonsession.h" #include "pythonexpression.h" +#include "pythonvariablemodel.h" #include "pythonhighlighter.h" #include "pythoncompletionobject.h" #include "pythonkeywords.h" #include "pythonutils.h" #include #include #include #include #include #include #include #include #include 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) { } void PythonSession::login() { qDebug()<<"login"; emit loginStarted(); // TODO: T6113, T6114 if (m_pProcess) m_pProcess->deleteLater(); m_pProcess = new KProcess(this); m_pProcess->setOutputChannelMode(KProcess::SeparateChannels); (*m_pProcess) << QStandardPaths::findExecutable(serverName); m_pProcess->start(); m_pProcess->waitForStarted(); m_pProcess->waitForReadyRead(); QTextStream stream(m_pProcess->readAllStandardOutput()); const QString& readyStatus = QString::fromLatin1("ready"); while (m_pProcess->state() == QProcess::Running) { const QString& rl = stream.readLine(); if (rl == readyStatus) break; } if (!QDBusConnection::sessionBus().isConnected()) { qWarning() << "Can't connect to the D-Bus session bus.\n" "To start it, run: eval `dbus-launch --auto-syntax`"; return; } const QString& serviceName = DbusChannelName + QString::fromLatin1("-%1").arg(m_pProcess->pid()); m_pIface = new QDBusInterface(serviceName, QString::fromLatin1("/"), QString(), QDBusConnection::sessionBus()); if (!m_pIface->isValid()) { qWarning() << QDBusConnection::sessionBus().lastError().message(); 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(); } void PythonSession::logout() { // TODO: T6113, T6114 m_pProcess->terminate(); m_variableModel->clearVariables(); - emit clearVariables(); qDebug()<<"logout"; changeStatus(Status::Disable); } void PythonSession::interrupt() { // TODO: T6113, T6114 if (m_pProcess->pid()) m_pProcess->kill(); qDebug()<<"interrupt"; foreach(Cantor::Expression* e, m_runningExpressions) e->interrupt(); m_runningExpressions.clear(); changeStatus(Cantor::Session::Done); } Cantor::Expression* PythonSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; PythonExpression* expr = new PythonExpression(this, internal); changeStatus(Cantor::Session::Running); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void PythonSession::runExpression(PythonExpression* expr) { qDebug() << "run expression"; m_currentExpression = expr; const QString& command = expr->internalCommand(); readExpressionOutput(command); } // Is called asynchronously in the Python3 plugin void PythonSession::readExpressionOutput(const QString& commandProcessing) { readOutput(commandProcessing); } void PythonSession::getPythonCommandOutput(const QString& commandProcessing) { runPythonCommand(commandProcessing); m_output = getOutput(); m_error = getError(); } bool PythonSession::identifyKeywords(const QString& command) { QString verifyErrorImport; QString listKeywords; QString keywordsString; QString moduleImported; QString moduleVariable; getPythonCommandOutput(command); qDebug() << "verifyErrorImport: "; if(!m_error.isEmpty()){ qDebug() << "returned false"; return false; } moduleImported += identifyPythonModule(command); moduleVariable += identifyVariableModule(command); if((moduleVariable.isEmpty()) && (!command.endsWith(QLatin1String("*")))){ keywordsString = command.section(QLatin1String(" "), 3).remove(QLatin1String(" ")); } if(moduleVariable.isEmpty() && (command.endsWith(QLatin1String("*")))){ listKeywords += QString::fromLatin1("import %1\n" \ "print(dir(%1))\n" \ "del %1\n").arg(moduleImported); } if(!moduleVariable.isEmpty()){ listKeywords += QLatin1String("print(dir(") + moduleVariable + QLatin1String("))\n"); } if(!listKeywords.isEmpty()){ getPythonCommandOutput(listKeywords); keywordsString = m_output; keywordsString.remove(QLatin1String("'")); keywordsString.remove(QLatin1String(" ")); keywordsString.remove(QLatin1String("[")); keywordsString.remove(QLatin1String("]")); } QStringList keywordsList = keywordsString.split(QLatin1String(",")); PythonKeywords::instance()->loadFromModule(moduleVariable, keywordsList); qDebug() << "Module imported" << moduleImported; return true; } QString PythonSession::identifyPythonModule(const QString& command) const { QString module; if(command.contains(QLatin1String("import "))){ module = command.section(QLatin1String(" "), 1, 1); } qDebug() << "module identified" << module; return module; } QString PythonSession::identifyVariableModule(const QString& command) const { QString variable; if(command.contains(QLatin1String("import "))){ variable = command.section(QLatin1String(" "), 1, 1); } if((command.contains(QLatin1String("import "))) && (command.contains(QLatin1String(" as ")))){ variable = command.section(QLatin1String(" "), 3, 3); } if(command.contains(QLatin1String("from "))){ variable = QLatin1String(""); } qDebug() << "variable identified" << variable; return variable; } void PythonSession::expressionFinished() { qDebug()<< "finished"; PythonExpression* expression = qobject_cast(sender()); m_runningExpressions.removeAll(expression); qDebug() << "size: " << m_runningExpressions.size(); } void PythonSession::updateOutput() { + m_needUpdate |= !m_currentExpression->isInternal(); if(m_error.isEmpty()){ m_currentExpression->parseOutput(m_output); qDebug() << "output: " << m_output; } else { m_currentExpression->parseError(m_error); qDebug() << "error: " << m_error; } - listVariables(); + if (m_needUpdate) + { + m_variableModel->update(); + m_needUpdate = false; + } changeStatus(Cantor::Session::Done); } void PythonSession::readOutput(const QString& commandProcessing) { qDebug() << "readOutput"; getPythonCommandOutput(commandProcessing); 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); - - return highlighter; + return new PythonHighlighter(parent, this, m_pythonVersion); } Cantor::CompletionObject* PythonSession::completionFor(const QString& command, int index) { return new PythonCompletionObject(command, index, this); } -QAbstractItemModel* PythonSession::variableModel() +Cantor::DefaultVariableModel* PythonSession::variableModel() const { return m_variableModel; } void PythonSession::runPythonCommand(const QString& command) const { // TODO: T6113, T6114 m_pIface->call(QString::fromLatin1("runPythonCommand"), command); } QString PythonSession::getOutput() const { // TODO: T6113, T6114 const QDBusReply& reply = m_pIface->call(QString::fromLatin1("getOutput")); if (reply.isValid()) return reply.value(); return reply.error().message(); } QString PythonSession::getError() const { // TODO: T6113, T6114 const QDBusReply& reply = m_pIface->call(QString::fromLatin1("getError")); if (reply.isValid()) return reply.value(); return reply.error().message(); } void PythonSession::setWorksheetPath(const QString& path) { worksheetPath = path; } diff --git a/src/backends/python/pythonsession.h b/src/backends/python/pythonsession.h index b2159960..aafc2b8b 100644 --- a/src/backends/python/pythonsession.h +++ b/src/backends/python/pythonsession.h @@ -1,106 +1,103 @@ /* 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) 2012 Filipe Saraiva Copyright (C) 2015 Minh Ngo */ #ifndef _PYTHONSESSION_H #define _PYTHONSESSION_H #include "session.h" #include #include namespace Cantor { -class DefaultVariableModel; + class DefaultVariableModel; } class PythonExpression; +class PythonVariableModel; class KDirWatch; class QDBusInterface; class KProcess; class CANTOR_PYTHONBACKEND_EXPORT PythonSession : public Cantor::Session { Q_OBJECT public: PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName, const QString DbusChannelName); ~PythonSession() override = default; void login() override; void logout() override; void interrupt() override; void runExpression(PythonExpression* expr); 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; + Cantor::DefaultVariableModel* variableModel() const override; void setWorksheetPath(const QString& path) override; 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; QDBusInterface* m_pIface; KProcess* m_pProcess; QString serverName; QString DbusChannelName; 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; QString identifyVariableModule(const QString& command) const; bool identifyKeywords(const QString& command); void runPythonCommand(const QString& command) const; QString getOutput() const; QString getError() const; virtual void readExpressionOutput(const QString& commandProcessing); protected: void updateOutput(); 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/pythonvariablemodel.cpp b/src/backends/python/pythonvariablemodel.cpp new file mode 100644 index 00000000..cb934a33 --- /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/python2/testpython2.h b/src/backends/python/pythonvariablemodel.h similarity index 59% copy from src/backends/python2/testpython2.h copy to src/backends/python/pythonvariablemodel.h index ed068c88..0dab8839 100644 --- a/src/backends/python2/testpython2.h +++ b/src/backends/python/pythonvariablemodel.h @@ -1,40 +1,43 @@ /* 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) 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/settings.ui b/src/backends/python/settings.ui index 7cbd5d2c..ac787f0b 100644 --- a/src/backends/python/settings.ui +++ b/src/backends/python/settings.ui @@ -1,71 +1,81 @@ PythonSettingsBase 0 0 400 300 Path to local documentation: Integrate Plots in Worksheet + + + + + Let Cantor follow the creation/destruction of variables + + + Enable Variable Management + + Scripts to autorun Qt::Vertical 20 40 KUrlRequester QFrame
kurlrequester.h
diff --git a/src/backends/python2/python2backend.cpp b/src/backends/python2/python2backend.cpp index 24d8767b..87c9c052 100644 --- a/src/backends/python2/python2backend.cpp +++ b/src/backends/python2/python2backend.cpp @@ -1,78 +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) 2014, 2015 Minh Ngo */ #include "python2backend.h" #include "python2session.h" #include "cantor_macros.h" #include "settings.h" #include Python2Backend::Python2Backend(QObject* parent, const QList args) : PythonBackend(parent, args) { setObjectName(QLatin1String("python2backend")); // Because the plugin may not have been loaded with // ExportExternalSymbols, we load the python symbols again // to make sure that python modules such as numpy see them // (see bug #330032) QLibrary pythonLib(QLatin1String("python2.7")); pythonLib.setLoadHints(QLibrary::ExportExternalSymbolsHint); pythonLib.load(); } Cantor::Session* Python2Backend::createSession() { return new Python2Session(this); } QString Python2Backend::id() const { return QLatin1String("python2"); } QString Python2Backend::version() const { 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(); if (!localDoc.isEmpty()) return localDoc; else return QUrl(i18nc("the url to the documentation Python 2", "http://docs.python.org/2/")); } QString Python2Backend::description() const { return i18n("

Python is a remarkably powerful dynamic programming language that is used in a wide variety of application domains. " \ "There are several Python packages to scientific programming.

" \ "

This backend supports Python 2.

"); } KConfigSkeleton* Python2Backend::config() const { return PythonSettings::self(); } K_PLUGIN_FACTORY_WITH_JSON(python2backend, "python2backend.json", registerPlugin();) #include "python2backend.moc" diff --git a/src/backends/python2/python2backend.h b/src/backends/python2/python2backend.h index 1d910c75..13117b75 100644 --- a/src/backends/python2/python2backend.h +++ b/src/backends/python2/python2backend.h @@ -1,42 +1,43 @@ /* 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) 2014, 2015 Minh Ngo */ #ifndef _PYTHON2BACKEND_H #define _PYTHON2BACKEND_H #include "../python/pythonbackend.h" class Python2Backend : public PythonBackend { Q_OBJECT public: explicit Python2Backend(QObject* parent = nullptr, const QList args = QList()); Cantor::Session* createSession() override; QString id() const override; QString version() const override; + Cantor::Backend::Capabilities capabilities() const override; QUrl helpUrl() const override; QString description() const override; KConfigSkeleton* config() const override; }; #endif diff --git a/src/backends/python2/python2backend.kcfg b/src/backends/python2/python2backend.kcfg index d461f16e..12afafd9 100644 --- a/src/backends/python2/python2backend.kcfg +++ b/src/backends/python2/python2backend.kcfg @@ -1,19 +1,23 @@ false + + + true + diff --git a/src/backends/python2/python2session.cpp b/src/backends/python2/python2session.cpp index 9ff5c69d..7ed92280 100644 --- a/src/backends/python2/python2session.cpp +++ b/src/backends/python2/python2session.cpp @@ -1,46 +1,51 @@ /* 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) 2015 Minh Ngo */ #include "python2session.h" #include "settings.h" #include "../python/pythonexpression.h" #include "../python/pythonkeywords.h" #include #include #include #include #include Python2Session::Python2Session(Cantor::Backend* backend) : PythonSession(backend, 2, QLatin1String("cantor_python2server"), QLatin1String("org.kde.Cantor.Python2")) { } bool Python2Session::integratePlots() const { return PythonSettings::integratePlots(); } QStringList Python2Session::autorunScripts() const { return PythonSettings::autorunScripts(); } + +bool Python2Session::variableManagement() const +{ + return PythonSettings::variableManagement(); +} diff --git a/src/backends/python2/python2session.h b/src/backends/python2/python2session.h index 40004801..3a54af93 100644 --- a/src/backends/python2/python2session.h +++ b/src/backends/python2/python2session.h @@ -1,35 +1,36 @@ /* 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) 2015 Minh Ngo */ #ifndef _PYTHON2SESSION_H #define _PYTHON2SESSION_H #include "../python/pythonsession.h" class Python2Session : public PythonSession { public: explicit Python2Session(Cantor::Backend* backend); bool integratePlots() const override; QStringList autorunScripts() const override; + bool variableManagement() const override; }; #endif diff --git a/src/backends/python2/testpython2.cpp b/src/backends/python2/testpython2.cpp index ab965f90..1488bebd 100644 --- a/src/backends/python2/testpython2.cpp +++ b/src/backends/python2/testpython2.cpp @@ -1,87 +1,151 @@ /* 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) 2013 Tuukka Verho */ #include "testpython2.h" #include "session.h" #include "backend.h" #include "expression.h" #include "result.h" +#include "defaultvariablemodel.h" QString TestPython2::backendName() { return QLatin1String("python2"); } void TestPython2::testImportNumpy() { Cantor::Expression* e = evalExp(QLatin1String("import numpy")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); } void TestPython2::testCodeWithComments() { { Cantor::Expression* e = evalExp(QLatin1String("#comment\n1+2")); QVERIFY(e != nullptr); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } { Cantor::Expression* e = evalExp(QLatin1String(" #comment\n1+2")); QVERIFY(e != nullptr); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } } void TestPython2::testSimpleCode() { Cantor::Expression* e=evalExp( QLatin1String("2+2")); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); QString result=e->result()->toHtml(); QCOMPARE( cleanOutput(result ), QLatin1String("4") ); } void TestPython2::testMultilineCode() { Cantor::Expression* e=evalExp(QLatin1String( "a = 2+2\n" "b = 3+3\n" "print a,b" )); QVERIFY( e!=nullptr ); QVERIFY( e->result()!=nullptr ); 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 = 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 = 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/python2/testpython2.h b/src/backends/python2/testpython2.h index ed068c88..2f1474da 100644 --- a/src/backends/python2/testpython2.h +++ b/src/backends/python2/testpython2.h @@ -1,40 +1,44 @@ /* 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) 2013 Tuukka Verho */ #ifndef _TESTPYTHON2_H #define _TESTPYTHON2_H #include "backendtest.h" class TestPython2 : public BackendTest { Q_OBJECT private Q_SLOTS: void testImportNumpy(); void testCodeWithComments(); void testSimpleCode(); void testMultilineCode(); + void testVariablesCreatingFromCode(); + void testVariableCleanupAfterRestart(); + void testDictVariable(); + private: QString backendName() override; }; #endif /* _TESTPYTHON2_H */ diff --git a/src/backends/python3/python3backend.cpp b/src/backends/python3/python3backend.cpp index 31f11c50..3105b606 100644 --- a/src/backends/python3/python3backend.cpp +++ b/src/backends/python3/python3backend.cpp @@ -1,71 +1,86 @@ /* 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) 2014, 2015 Minh Ngo */ #include "python3backend.h" #include "python3session.h" #include "cantor_macros.h" #include "settings.h" #include Python3Backend::Python3Backend(QObject* parent, const QList& args) : PythonBackend(parent, args) { setObjectName(QLatin1String("python3backend")); } Cantor::Session* Python3Backend::createSession() { return new Python3Session(this); } QString Python3Backend::id() const { return QLatin1String("python3"); } QString Python3Backend::version() const { 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(); if (!localDoc.isEmpty()) return localDoc; else return QUrl(i18nc("the url to the documentation Python 3", "http://docs.python.org/3/")); } QString Python3Backend::description() const { return i18n("

Python is a remarkably powerful dynamic programming language that is used in a wide variety of application domains. " \ "There are several Python packages to scientific programming.

" \ "

This backend supports Python 3.

"); } KConfigSkeleton* Python3Backend::config() const { return PythonSettings::self(); } K_PLUGIN_FACTORY_WITH_JSON(python3backend, "python3backend.json", registerPlugin();) #include "python3backend.moc" diff --git a/src/backends/python3/python3backend.h b/src/backends/python3/python3backend.h index f0ee1339..0ca67ea1 100644 --- a/src/backends/python3/python3backend.h +++ b/src/backends/python3/python3backend.h @@ -1,41 +1,42 @@ /* 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) 2014, 2015 Minh Ngo */ #ifndef _PYTHON3BACKEND_H #define _PYTHON3BACKEND_H #include "../python/pythonbackend.h" class Python3Backend : public PythonBackend { public: explicit Python3Backend(QObject* parent = nullptr, const QList& args = QList()); Cantor::Session* createSession() override; QString id() const override; QString version() const override; + Cantor::Backend::Capabilities capabilities() const override; QUrl helpUrl() const override; QString description() const override; KConfigSkeleton* config() const override; }; #endif diff --git a/src/backends/python3/python3backend.kcfg b/src/backends/python3/python3backend.kcfg index 3ceb9800..4e9e6daa 100644 --- a/src/backends/python3/python3backend.kcfg +++ b/src/backends/python3/python3backend.kcfg @@ -1,19 +1,23 @@ false + + + true + diff --git a/src/backends/python3/python3session.cpp b/src/backends/python3/python3session.cpp index c33c4a78..81366d00 100644 --- a/src/backends/python3/python3session.cpp +++ b/src/backends/python3/python3session.cpp @@ -1,37 +1,43 @@ /* 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) 2015 Minh Ngo */ #include "python3session.h" #include "settings.h" Python3Session::Python3Session(Cantor::Backend* backend) : PythonSession(backend, 3, QLatin1String("cantor_python3server"), QLatin1String("org.kde.Cantor.Python3")) { } bool Python3Session::integratePlots() const { return PythonSettings::integratePlots(); } QStringList Python3Session::autorunScripts() const { return PythonSettings::autorunScripts(); } + +bool Python3Session::variableManagement() const +{ + return PythonSettings::variableManagement(); +} + diff --git a/src/backends/python3/python3session.h b/src/backends/python3/python3session.h index 9abe8616..c859ace6 100644 --- a/src/backends/python3/python3session.h +++ b/src/backends/python3/python3session.h @@ -1,35 +1,36 @@ /* 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) 2015 Minh Ngo */ #ifndef _PYTHON3SESSION_H #define _PYTHON3SESSION_H #include "../python/pythonsession.h" class Python3Session : public PythonSession { public: explicit Python3Session(Cantor::Backend* backend); bool integratePlots() const override; QStringList autorunScripts() const override; + bool variableManagement() const override; }; #endif diff --git a/src/backends/python3/testpython3.cpp b/src/backends/python3/testpython3.cpp index 9841a217..03af0138 100644 --- a/src/backends/python3/testpython3.cpp +++ b/src/backends/python3/testpython3.cpp @@ -1,118 +1,181 @@ /* 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) 2015 Minh Ngo */ #include "testpython3.h" #include "session.h" #include "backend.h" #include "expression.h" #include "imageresult.h" +#include "defaultvariablemodel.h" QString TestPython3::backendName() { return QLatin1String("python3"); } void TestPython3::testImportNumpy() { Cantor::Expression* e = evalExp(QLatin1String("import numpy")); QVERIFY(e != nullptr); QCOMPARE(e->status(), Cantor::Expression::Done); } void TestPython3::testCodeWithComments() { { Cantor::Expression* e = evalExp(QLatin1String("#comment\n1+2")); QVERIFY(e != nullptr); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } { Cantor::Expression* e = evalExp(QLatin1String(" #comment\n1+2")); QVERIFY(e != nullptr); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } } void TestPython3::testPython3Code() { { Cantor::Expression* e = evalExp(QLatin1String("print 1 + 2")); QVERIFY(e != nullptr); QVERIFY(!e->errorMessage().isEmpty()); } { Cantor::Expression* e = evalExp(QLatin1String("print(1 + 2)")); QVERIFY(e != nullptr); QVERIFY(e->result()->data().toString() == QLatin1String("3")); } } void TestPython3::testSimplePlot() { Cantor::Expression* e = evalExp(QLatin1String( "import matplotlib\n" "import matplotlib.pyplot as plt\n" "import numpy as np" )); QVERIFY(e != nullptr); QVERIFY(e->errorMessage().isEmpty() == true); //the variable model shouldn't have any entries after the module imports QAbstractItemModel* model = session()->variableModel(); QVERIFY(model != nullptr); QVERIFY(model->rowCount() == 0); //create data for plotting e = evalExp(QLatin1String( "t = np.arange(0.0, 2.0, 0.01)\n" "s = 1 + np.sin(2 * np.pi * t)" )); QVERIFY(e != nullptr); QVERIFY(e->errorMessage().isEmpty() == true); //the variable model should have two entries now QVERIFY(model->rowCount() == 2); //plot the data and check the results e = evalExp(QLatin1String( "plt.plot(t,s)\n" "plt.show()" )); waitForSignal(e, SIGNAL(gotResult())); QVERIFY(e != nullptr); QVERIFY(e->errorMessage().isEmpty() == true); QVERIFY(model->rowCount() == 2); //still only two variables //there must be one single image result 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 = 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 = 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/python3/testpython3.h b/src/backends/python3/testpython3.h index 18aa4c38..1645f8b5 100644 --- a/src/backends/python3/testpython3.h +++ b/src/backends/python3/testpython3.h @@ -1,39 +1,43 @@ /* 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) 2015 Minh Ngo */ #ifndef _TESTPYTHON3_H #define _TESTPYTHON3_H #include class TestPython3 : public BackendTest { Q_OBJECT private Q_SLOTS: void testImportNumpy(); void testCodeWithComments(); void testPython3Code(); void testSimplePlot(); + void testVariablesCreatingFromCode(); + void testVariableCleanupAfterRestart(); + void testDictVariable(); + private: QString backendName() override; }; #endif /* _TESTPYTHON3_H */ diff --git a/src/backends/qalculate/qalculateexpression.cpp b/src/backends/qalculate/qalculateexpression.cpp index 40c53cdc..e17a11b3 100644 --- a/src/backends/qalculate/qalculateexpression.cpp +++ b/src/backends/qalculate/qalculateexpression.cpp @@ -1,884 +1,883 @@ /************************************************************************************* * Copyright (C) 2009 by Milian Wolff * * Copyright (C) 2011 by Matteo Agostinelli * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include #include "textresult.h" #include "helpresult.h" #include "imageresult.h" #include "epsresult.h" #include "settings.h" #include "defaultvariablemodel.h" #include "qalculateexpression.h" #include "qalculatesession.h" #include "qalculatesyntaxhelpobject.h" #include #include #include #include #include #include #include // required for the plotting interface of Qalculator #include #include #include #include #include #include #include #include #include QalculateExpression::QalculateExpression( QalculateSession* session, bool internal) : Cantor::Expression(session, internal), m_tempFile(nullptr) { } QalculateExpression::~QalculateExpression() { if (m_tempFile) delete m_tempFile; } void QalculateExpression::evaluate() { /* Use Api for: * help * plot Use qalc for any other command */ setStatus(Cantor::Expression::Computing); if (command().isEmpty()) { setStatus(Cantor::Expression::Done); return; } QStringList commands = command().split(QLatin1Char('\n')); foreach(const QString& command, commands) { if (command.contains(QLatin1String("help"))) { QalculateSyntaxHelpObject* helper = new QalculateSyntaxHelpObject(command, (QalculateSession*) session()); setResult(new Cantor::HelpResult(helper->answer())); setStatus(Cantor::Expression::Done); return; } else if (command.trimmed().startsWith(QLatin1String("plot")) && (command.indexOf(QLatin1String("plot"))+4 == command.size() || command[command.indexOf(QLatin1String("plot"))+4].isSpace())) { evaluatePlotCommand(); return; } } // we are here because the commands entered by user are regular commands. We would have returned by now otherwise QalculateSession* currentSession = dynamic_cast(session()); currentSession->runExpression(); } void QalculateExpression::parseOutput(QString& output) { output.remove(QLatin1String(">")); output = output.trimmed(); qDebug() << "output from qalc for command: " << command() << " " << output << endl; setResult(new Cantor::TextResult(output)); // update the variable model updateVariables(); setStatus(Cantor::Expression::Done); } void QalculateExpression::updateVariables() { QalculateSession* currentSession = dynamic_cast(session()); QMap &variables = currentSession->variables; - Cantor::DefaultVariableModel* model = static_cast(currentSession->variableModel()); QMap::const_iterator it = variables.constBegin(); while (it != variables.constEnd()) { - model->addVariable(it.key(), it.value()); + currentSession->variableModel()->addVariable(it.key(), it.value()); ++it; } } void QalculateExpression::parseError(QString& error) { error.remove(QLatin1String(">")); error = error.trimmed(); qDebug() << "Error from qalc for command: " << command() << " " << error << endl; setErrorMessage(error); setStatus(Cantor::Expression::Error); } void QalculateExpression::interrupt() { setStatus(Cantor::Expression::Interrupted); } void QalculateExpression::evaluatePlotCommand() { QString argString = command().mid(command().indexOf(QLatin1String("plot"))+4); argString = QLatin1String(unlocalizeExpression(argString).c_str()); argString = argString.trimmed(); QList argumentsList; QStringList arguments; // For error handling KColorScheme scheme(QApplication::palette().currentColorGroup()); const QString errorColor = scheme.foreground(KColorScheme::NegativeText).color().name(); const QString warningColor = scheme.foreground(KColorScheme::NeutralText).color().name(); const QString msgFormat(QLatin1String("%2: %3
\n")); if (!CALCULATOR->canPlot()) { showMessage(i18n("Qalculate reports it cannot print. Is gnuplot installed?"), MESSAGE_ERROR); return; } // Split argString into the arguments int i=0; int j=0; QString arg = QLatin1String(""); while (i < argString.size()) { if (argString[i] == QLatin1Char('"') || argString[i] == QLatin1Char('\'')) { ++j; while(j < argString.size() && argString[j] != argString[i]) { if (argString[j] == QLatin1Char('\\')) { ++j; if (j == argString.size()) continue; // just ignore trailing backslashes } arg += argString[j]; ++j; } if (j == argString.size()) { showMessage(i18n("missing %1", argString[i]), MESSAGE_ERROR); return; } ++j; } else if (argString[i] == QLatin1Char(',')) { argumentsList.append(arguments); arguments.clear(); ++j; } else { while(j < argString.size() && !argString[j].isSpace() && argString[j] != QLatin1Char('=') && argString[j] != QLatin1Char(',')) { if (argString[j] == QLatin1Char('\\')) { ++j; if (j == argString.size()) continue; // just ignore trailing backslashes } arg += argString[j]; ++j; } } if (argString[j] == QLatin1Char('=')) { // Parse things like title="..." as one argument arg += QLatin1Char('='); i = ++j; continue; } if (!arg.isEmpty()) { arguments << arg; arg = QLatin1String(""); } while (j < argString.size() && argString[j].isSpace()) ++j; i = j; } argumentsList.append(arguments); // Parse the arguments and compute the points to be plotted std::vector y_vectors; std::vector x_vectors; std::vector plotDataParameterList; PlotParameters plotParameters; EvaluationOptions eo = evaluationOptions(); /// temporary plotParameters.title = ""; plotParameters.y_label = ""; plotParameters.x_label = ""; plotParameters.filename = ""; plotParameters.filetype = PLOT_FILETYPE_AUTO; plotParameters.color = QalculateSettings::coloredPlot(); plotParameters.auto_y_min = true; plotParameters.auto_x_min = true; plotParameters.auto_x_max = true; plotParameters.auto_y_max = true; plotParameters.y_log = false; plotParameters.x_log = false; plotParameters.grid = QalculateSettings::plotGrid(); plotParameters.linewidth = QalculateSettings::plotLineWidth(); plotParameters.show_all_borders = QalculateSettings::plotBorder(); switch (QalculateSettings::plotLegend()) { case QalculateSettings::LEGEND_NONE: plotParameters.legend_placement = PLOT_LEGEND_NONE; break; case QalculateSettings::LEGEND_TOP_LEFT: plotParameters.legend_placement = PLOT_LEGEND_TOP_LEFT; break; case QalculateSettings::LEGEND_TOP_RIGHT: plotParameters.legend_placement = PLOT_LEGEND_TOP_RIGHT; break; case QalculateSettings::LEGEND_BOTTOM_LEFT: plotParameters.legend_placement = PLOT_LEGEND_BOTTOM_LEFT; break; case QalculateSettings::LEGEND_BOTTOM_RIGHT: plotParameters.legend_placement = PLOT_LEGEND_BOTTOM_RIGHT; break; case QalculateSettings::LEGEND_BELOW: plotParameters.legend_placement = PLOT_LEGEND_BELOW; break; case QalculateSettings::LEGEND_OUTSIDE: plotParameters.legend_placement = PLOT_LEGEND_OUTSIDE; break; } bool plotInline = QalculateSettings::inlinePlot(); MathStructure xMin; MathStructure xMax; xMin.setUndefined(); xMax.setUndefined(); MathStructure stepLength; stepLength.setUndefined(); int steps = QalculateSettings::plotSteps(); QString mustBeNumber = i18n("%1 must be a number."); QString mustBeInteger = i18n("%1 must be a integer."); QString mustBeBoolean = i18n("%1 must be a boolean."); QString invalidOption = i18n("invalid option for %1: %2"); for (int i = 0; i < argumentsList.size(); ++i) { std::string xVariable = "x"; PlotDataParameters* plotDataParams = new PlotDataParameters; plotDataParameterList.push_back(plotDataParams); plotDataParams->title = ""; switch(QalculateSettings::plotSmoothing()) { case QalculateSettings::SMOOTHING_NONE: plotDataParams->smoothing = PLOT_SMOOTHING_NONE; break; case QalculateSettings::SMOOTHING_UNIQUE: plotDataParams->smoothing = PLOT_SMOOTHING_UNIQUE; break; case QalculateSettings::SMOOTHING_CSPLINES: plotDataParams->smoothing = PLOT_SMOOTHING_CSPLINES; break; case QalculateSettings::SMOOTHING_BEZIER: plotDataParams->smoothing = PLOT_SMOOTHING_BEZIER; break; case QalculateSettings::SMOOTHING_SBEZIER: plotDataParams->smoothing = PLOT_SMOOTHING_SBEZIER; break; } switch(QalculateSettings::plotStyle()) { case QalculateSettings::STYLE_LINES: plotDataParams->style = PLOT_STYLE_LINES; break; case QalculateSettings::STYLE_POINTS: plotDataParams->style = PLOT_STYLE_POINTS; break; case QalculateSettings::STYLE_LINES_POINTS: plotDataParams->style = PLOT_STYLE_POINTS_LINES; break; case QalculateSettings::STYLE_BOXES: plotDataParams->style = PLOT_STYLE_BOXES; break; case QalculateSettings::STYLE_HISTOGRAM: plotDataParams->style = PLOT_STYLE_HISTOGRAM; break; case QalculateSettings::STYLE_STEPS: plotDataParams->style = PLOT_STYLE_STEPS; break; case QalculateSettings::STYLE_CANDLESTICKS: plotDataParams->style = PLOT_STYLE_CANDLESTICKS; break; case QalculateSettings::STYLE_DOTS: plotDataParams->style = PLOT_STYLE_DOTS; break; } plotDataParams->yaxis2 = false; plotDataParams->xaxis2 = false; arguments = argumentsList[i]; std::string expression; int lastExpressionEntry = -1; for (int j = 0; j < arguments.size(); ++j) { QString argument = arguments[j]; // PlotParameters if (argument.startsWith(QLatin1String("plottitle="))) plotParameters.title = argument.mid(10).toLatin1().data(); else if (argument.startsWith(QLatin1String("ylabel="))) plotParameters.y_label = argument.mid(7).toLatin1().data(); else if (argument.startsWith(QLatin1String("xlabel="))) plotParameters.x_label = argument.mid(7).toLatin1().data(); else if (argument.startsWith(QLatin1String("filename="))) plotParameters.filename = argument.mid(9).toLatin1().data(); else if (argument.startsWith(QLatin1String("filetype="))) { QString option = argument.mid(9); if (option == QLatin1String("auto")) plotParameters.filetype = PLOT_FILETYPE_AUTO; else if (option == QLatin1String("png")) plotParameters.filetype = PLOT_FILETYPE_PNG; else if (option == QLatin1String("ps")) plotParameters.filetype = PLOT_FILETYPE_PS; else if (option == QLatin1String("eps")) plotParameters.filetype = PLOT_FILETYPE_EPS; else if (option == QLatin1String("latex")) plotParameters.filetype = PLOT_FILETYPE_LATEX; else if (option == QLatin1String("svg")) plotParameters.filetype = PLOT_FILETYPE_SVG; else if (option == QLatin1String("fig")) plotParameters.filetype = PLOT_FILETYPE_FIG; else { QString msg = invalidOption.arg(QLatin1String("filetype"), option); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("font="))) plotParameters.font = argument.mid(5).toLatin1().data(); else if (argument.startsWith(QLatin1String("color="))) { bool ok; plotParameters.color = stringToBool(argument.mid(6), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("color")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("ylog="))) { bool ok; plotParameters.y_log = stringToBool(argument.mid(5), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("ylog")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xlog="))) { bool ok; plotParameters.x_log = stringToBool(argument.mid(5), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("xlog")), MESSAGE_ERROR); return; } } else if (argument.startsWith(QLatin1String("ylogbase="))) { MathStructure ylogStr = CALCULATOR->calculate(argument.mid(9).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } if (ylogStr.isNumber()) { Number ylogNum = ylogStr.number(); plotParameters.y_log_base = ylogNum.floatValue(); } else { showMessage(mustBeNumber.arg(QLatin1String("ylogbase")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xlogbase="))) { MathStructure xlogStr = CALCULATOR->calculate(argument.mid(9).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } if (xlogStr.isNumber()) { Number xlogNum = xlogStr.number(); plotParameters.x_log_base = xlogNum.floatValue(); } else { showMessage(mustBeNumber.arg(QLatin1String("xlogbase")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("grid="))) { bool ok; plotParameters.grid = stringToBool(argument.mid(5), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("grid")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("linewidth="))) { MathStructure lineWidthStr = CALCULATOR->calculate(argument.mid(10).toLatin1().data(), eo); Number lineWidthNum; if (lineWidthStr.isNumber() && lineWidthStr.number().isInteger()) { lineWidthNum = lineWidthStr.number(); plotParameters.linewidth = lineWidthNum.intValue(); } else { showMessage(mustBeInteger.arg(QLatin1String("linewidth")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("border="))) { bool ok; plotParameters.show_all_borders = stringToBool(argument.mid(7), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("border")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("legend="))) { QString option = argument.mid(7); if (option == QLatin1String("none")) plotParameters.legend_placement = PLOT_LEGEND_NONE; else if (option == QLatin1String("top_left")) plotParameters.legend_placement = PLOT_LEGEND_TOP_LEFT; else if (option == QLatin1String("top_right")) plotParameters.legend_placement = PLOT_LEGEND_TOP_RIGHT; else if (option == QLatin1String("bottom_left")) plotParameters.legend_placement = PLOT_LEGEND_BOTTOM_LEFT; else if (option == QLatin1String("bottom_right")) plotParameters.legend_placement = PLOT_LEGEND_BOTTOM_RIGHT; else if (option == QLatin1String("below")) plotParameters.legend_placement = PLOT_LEGEND_BELOW; else if (option == QLatin1String("outside")) plotParameters.legend_placement = PLOT_LEGEND_OUTSIDE; else { QString msg = invalidOption.arg(QLatin1String("legend"), option); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } // PlotDataParameters else if (argument.startsWith(QLatin1String("title="))) { plotDataParams->title = argument.mid(6).toLatin1().data(); } else if (argument.startsWith(QLatin1String("smoothing="))) { QString option = argument.mid(10); if (option == QLatin1String("none")) plotDataParams->smoothing = PLOT_SMOOTHING_NONE; else if (option == QLatin1String("monotonic")) plotDataParams->smoothing = PLOT_SMOOTHING_UNIQUE; else if (option == QLatin1String("csplines")) plotDataParams->smoothing = PLOT_SMOOTHING_CSPLINES; else if (option == QLatin1String("bezier")) plotDataParams->smoothing = PLOT_SMOOTHING_BEZIER; else if (option == QLatin1String("sbezier")) plotDataParams->smoothing = PLOT_SMOOTHING_SBEZIER; else { QString msg = invalidOption.arg(QLatin1String("smoothing"), option); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("style="))) { QString option = argument.mid(6); if (option == QLatin1String("lines")) plotDataParams->style = PLOT_STYLE_LINES; else if (option == QLatin1String("points")) plotDataParams->style = PLOT_STYLE_POINTS; else if (option == QLatin1String("points_lines")) plotDataParams->style = PLOT_STYLE_POINTS_LINES; else if (option == QLatin1String("boxes")) plotDataParams->style = PLOT_STYLE_BOXES; else if (option == QLatin1String("histogram")) plotDataParams->style = PLOT_STYLE_HISTOGRAM; else if (option == QLatin1String("steps")) plotDataParams->style = PLOT_STYLE_STEPS; else if (option == QLatin1String("candlesticks")) plotDataParams->style = PLOT_STYLE_CANDLESTICKS; else if (option == QLatin1String("dots")) plotDataParams->style = PLOT_STYLE_DOTS; else { QString msg = invalidOption.arg(QLatin1String("style"), option); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xaxis2="))) { bool ok; plotDataParams->xaxis2 = stringToBool(argument.mid(7), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("xaxis2")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("yaxis2="))) { bool ok; plotDataParams->yaxis2 = stringToBool(argument.mid(7), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("yaxis2")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } // inline, xmin, xmax, step, steps, xvar // Custom options else if (argument.startsWith(QLatin1String("inline="))) { bool ok; plotInline = stringToBool(argument.mid(7), &ok); if (!ok) { showMessage(mustBeBoolean.arg(QLatin1String("inline")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xmin="))) { xMin = CALCULATOR->calculate(argument.mid(5).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xmax="))) { xMax = CALCULATOR->calculate(argument.mid(5).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("step="))) { stepLength = CALCULATOR->calculate(argument.mid(5).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } steps = -1; } else if (argument.startsWith(QLatin1String("steps="))) { MathStructure stepsStr = CALCULATOR->calculate(argument.mid(6).toLatin1().data(), eo); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } Number stepsNum; if (stepsStr.isNumber() && stepsStr.number().isInteger()) { stepsNum = stepsStr.number(); steps = stepsNum.intValue(); stepLength.setUndefined(); } else { showMessage(mustBeInteger.arg(QLatin1String("steps")), MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } else if (argument.startsWith(QLatin1String("xvar="))) { xVariable = argument.mid(5).toLatin1().data(); } else if (expression.empty()) { expression = argument.toLatin1().data(); lastExpressionEntry = j; } else if (lastExpressionEntry == j-1) { expression += " "; expression += argument.toLatin1().data(); lastExpressionEntry = j; } else { QString msg = i18n("found multiple expressions in one plot command (%1 and %2).", QLatin1String(expression.c_str()), argument); showMessage(msg, MESSAGE_ERROR); deletePlotDataParameters(plotDataParameterList); return; } } if (expression.empty()) continue; if (xMin.isUndefined()) { if (!plotParameters.auto_x_min) xMin = plotParameters.x_min; else xMin = 0.0; } if (xMax.isUndefined()) { if (!plotParameters.auto_x_max) xMax = plotParameters.x_max; else xMax = 10.0; } if (plotDataParams->title.empty()) plotDataParams->title = expression; MathStructure x_vec, y_vec; x_vec.clearVector(); if (!stepLength.isUndefined()) y_vec = CALCULATOR->expressionToPlotVector(expression, xMin, xMax, stepLength, &x_vec, xVariable, eo.parse_options); else y_vec = CALCULATOR->expressionToPlotVector(expression, xMin, xMax, steps, &x_vec, xVariable, eo.parse_options); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } x_vectors.push_back(x_vec); y_vectors.push_back(y_vec); //PrintOptions po; //y_vec.format(po); //setResult(new Cantor::TextResult(y_vec.print(po).c_str())); //setStatus(Done); //deletePlotDataParameters(plotDataParameterList); //return; } if (plotInline && plotParameters.filename.empty()) { // TODO: get a temporary file name here if (!m_tempFile) { #ifdef WITH_EPS m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_qalculate-XXXXXX.eps" )); #else m_tempFile=new QTemporaryFile(QDir::tempPath() + QLatin1String("/cantor_qalculate-XXXXXX.png")); #endif m_tempFile->open(); } plotParameters.filename = m_tempFile->fileName().toLatin1().data(); plotParameters.filetype = PLOT_FILETYPE_AUTO; } CALCULATOR->plotVectors(&plotParameters, y_vectors, x_vectors, plotDataParameterList); if (checkForCalculatorMessages() & (MSG_WARN|MSG_ERR)) { deletePlotDataParameters(plotDataParameterList); return; } deletePlotDataParameters(plotDataParameterList); if (plotInline) { #ifdef WITH_EPS size_t p = plotParameters.filename.size(); if (plotParameters.filetype == PLOT_FILETYPE_EPS || plotParameters.filetype == PLOT_FILETYPE_PS || (plotParameters.filetype == PLOT_FILETYPE_AUTO && p >= 4 && plotParameters.filename.substr(p-4,4) == ".eps") || (plotParameters.filetype == PLOT_FILETYPE_AUTO && p >= 3 && plotParameters.filename.substr(p-3,3) == ".ps")) setResult(new Cantor::EpsResult(QUrl(QString::fromStdString(plotParameters.filename)))); else setResult(new Cantor::ImageResult(QUrl(QString::fromStdString(plotParameters.filename)))); #else setResult(new Cantor::ImageResult(QUrl::fromLocalFile(QString::fromStdString(plotParameters.filename)))); #endif setStatus(Cantor::Expression::Done); } } void QalculateExpression::showMessage(QString msg, MessageType mtype) { KColorScheme scheme(QApplication::palette().currentColorGroup()); const QString errorColor = scheme.foreground(KColorScheme::NegativeText).color().name(); const QString warningColor = scheme.foreground(KColorScheme::NeutralText).color().name(); const QString msgFormat(QLatin1String("%2: %3
\n")); if(mtype == MESSAGE_ERROR || mtype == MESSAGE_WARNING) { msg.replace(QLatin1String("&"), QLatin1String("&")); msg.replace(QLatin1String(">"), QLatin1String(">")); msg.replace(QLatin1String("<"), QLatin1String("<")); if (mtype == MESSAGE_ERROR) { msg = msgFormat.arg(errorColor, i18n("ERROR"), QLatin1String(msg.toLatin1().data())); } else { msg = msgFormat.arg(errorColor, i18n("WARNING"), QLatin1String(msg.toLatin1().data())); } setErrorMessage(msg); setStatus(Error); } else { KMessageBox::information(QApplication::activeWindow(), msg); } } EvaluationOptions QalculateExpression::evaluationOptions() { EvaluationOptions eo; eo.auto_post_conversion = QalculateSettings::postConversion() ? POST_CONVERSION_BEST : POST_CONVERSION_NONE; eo.keep_zero_units = false; eo.parse_options = parseOptions(); switch (QalculateSettings::structuring()) { case 0: eo.structuring = STRUCTURING_NONE; break; case 1: eo.structuring = STRUCTURING_SIMPLIFY; break; case 2: eo.structuring = STRUCTURING_FACTORIZE; break; } return eo; } ParseOptions QalculateExpression::parseOptions() { ParseOptions po; switch (QalculateSettings::angleUnit()) { case 0: po.angle_unit = ANGLE_UNIT_NONE; break; case 1: po.angle_unit = ANGLE_UNIT_RADIANS; break; case 2: po.angle_unit = ANGLE_UNIT_DEGREES; break; case 3: po.angle_unit = ANGLE_UNIT_GRADIANS; break; } po.base = QalculateSettings::base(); po.comma_as_separator = false; return po; } void QalculateExpression::deletePlotDataParameters (const std::vector& plotDataParameterList) { for(size_t i = 0; i < plotDataParameterList.size(); ++i) delete plotDataParameterList[i]; } bool QalculateExpression::stringToBool(const QString &string, bool *ok) { if (string == QLatin1String("true") || string == QLatin1String("1")) { *ok = true; return true; } else if (string == QLatin1String("false") || string == QLatin1String("0")) { *ok = true; return false; } else { *ok = false; return false; } } int QalculateExpression::checkForCalculatorMessages() { // error handling, most of it copied from qalculate-kde int msgType = MSG_NONE; if ( CALCULATOR->message() ) { QString msg; KColorScheme scheme(QApplication::palette().currentColorGroup()); const QString errorColor = scheme.foreground(KColorScheme::NegativeText).color().name(); const QString warningColor = scheme.foreground(KColorScheme::NeutralText).color().name(); const QString msgFormat(QLatin1String("%2: %3
\n")); MessageType mtype; while(true) { mtype = CALCULATOR->message()->type(); switch(mtype) { case MESSAGE_INFORMATION: msgType |= MSG_INFO; break; case MESSAGE_WARNING: msgType |= MSG_WARN; break; case MESSAGE_ERROR: msgType |= MSG_ERR; break; } if(mtype == MESSAGE_ERROR || mtype == MESSAGE_WARNING) { QString text = QLatin1String(CALCULATOR->message()->message().c_str()); text.replace(QLatin1String("&"), QLatin1String("&")); text.replace(QLatin1String(">"), QLatin1String(">")); text.replace(QLatin1String("<"), QLatin1String("<")); if (mtype == MESSAGE_ERROR) { msg.append(msgFormat.arg(errorColor, i18n("ERROR"), text)); } else { msg.append(msgFormat.arg(errorColor, i18n("WARNING"), text)); } } else { KMessageBox::information(QApplication::activeWindow(), QLatin1String(CALCULATOR->message()->message().c_str())); } if(!CALCULATOR->nextMessage()) break; } if ( !msg.isEmpty() ) { m_message += msg; setErrorMessage(m_message); setStatus(Error); } } return msgType; } std::string QalculateExpression::unlocalizeExpression(QString expr) { // copy'n'pasted from qalculate plasma applet return CALCULATOR->unlocalizeExpression( expr.replace(QChar(0xA3), QLatin1String("GBP")) .replace(QChar(0xA5), QLatin1String("JPY")) .replace(QLatin1String("$"), QLatin1String("USD")) .replace(QChar(0x20AC), QLatin1String("EUR")) .toLatin1().data() ); } QSharedPointer QalculateExpression::printOptions() { QSharedPointer po(new PrintOptions); switch (QalculateSettings::fractionFormat()) { case 0: po->number_fraction_format = FRACTION_DECIMAL; break; case 1: po->number_fraction_format = FRACTION_DECIMAL_EXACT; break; case 2: po->number_fraction_format = FRACTION_FRACTIONAL; break; case 3: po->number_fraction_format = FRACTION_COMBINED; break; } po->indicate_infinite_series = QalculateSettings::indicateInfiniteSeries(); po->use_all_prefixes = QalculateSettings::useAllPrefixes(); po->negative_exponents = QalculateSettings::negativeExponents(); po->lower_case_e = true; po->base = QalculateSettings::base(); po->decimalpoint_sign = QLocale().decimalPoint().toLatin1(); switch (QalculateSettings::minExp()) { case 0: po->min_exp = EXP_NONE; break; case 1: po->min_exp = EXP_PURE; break; case 2: po->min_exp = EXP_SCIENTIFIC; break; case 3: po->min_exp = EXP_PRECISION; break; case 4: po->min_exp = EXP_BASE_3; break; } return po; } diff --git a/src/backends/qalculate/qalculatesession.cpp b/src/backends/qalculate/qalculatesession.cpp index 8a9a9fa3..dfe212d6 100644 --- a/src/backends/qalculate/qalculatesession.cpp +++ b/src/backends/qalculate/qalculatesession.cpp @@ -1,430 +1,430 @@ /************************************************************************************ * Copyright (C) 2009 by Milian Wolff * * Copyright (C) 2011 by Matteo Agostinelli * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "settings.h" #include "qalculatesession.h" #include "qalculatecompletionobject.h" #include "qalculatehighlighter.h" #include "defaultvariablemodel.h" #include #include #include #include #include #include #include #include #include #include #include "qalculatesyntaxhelpobject.h" QalculateSession::QalculateSession( Cantor::Backend* backend) : Session(backend), m_variableModel(new Cantor::DefaultVariableModel(this)), m_process(nullptr), m_currentExpression(nullptr), m_isSaveCommand(false) { /* qalc does all of this by default but we still need the CALCULATOR instance for plotting graphs */ if ( !CALCULATOR ) { new Calculator(); CALCULATOR->loadGlobalDefinitions(); CALCULATOR->loadLocalDefinitions(); CALCULATOR->loadExchangeRates(); } } QalculateSession::~QalculateSession() { CALCULATOR->abort(); if(m_process) m_process->kill(); } void QalculateSession::login() { emit loginStarted(); /* we will , most probably, use autoscripts for setting the mode , evaluate options, print options etc */ // if(!QalculateSettings::autorunScripts().isEmpty()){ // QString autorunScripts = QalculateSettings::self()->autorunScripts().join(QLatin1String("\n")); // // evaluateExpression(autorunScripts, QalculateExpression::DeleteOnFinish); // } /* set up the process here. The program path , arguments(if any),channel modes , and connections should all be set up here. once the setup is complete, start the process and inform the worksheet that we are ready */ m_process = new QProcess(this); m_process->setProgram(QStandardPaths::findExecutable(QLatin1String("qalc"))); m_process->setProcessChannelMode(QProcess::SeparateChannels); connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readError())); connect(m_process, SIGNAL(started()), this, SLOT(processStarted())); m_process->start(); changeStatus(Session::Done); emit loginDone(); } void QalculateSession::readOutput() { while(m_process->bytesAvailable()) { m_output.append(QString::fromLocal8Bit(m_process->readLine())); qDebug() << m_output << endl; } if(m_currentExpression && !m_output.isEmpty() && m_output.trimmed().endsWith(QLatin1String(">"))) { // check if the commandQueue is empty or not . if it's not empty run the "runCommandQueue" function. // store the output in finalOutput and clear m_output if(m_currentCommand.trimmed().isEmpty()) m_output.clear(); if(!m_output.toLower().contains(QLatin1String("error")) && m_isSaveCommand) { storeVariables(m_currentCommand, m_output); m_isSaveCommand = false; } m_output = m_output.trimmed(); m_output.remove(m_currentCommand); if (!m_output.isEmpty()) m_finalOutput.append(m_output); // we tried to perform a save operation but failed(see parseSaveCommand()).In such a case // m_output will be empty but m_saveError will contain the error message. if(!m_saveError.isEmpty()) { m_finalOutput.append(m_saveError); m_saveError.clear(); } m_finalOutput.append(QLatin1String("\n")); m_output.clear(); if (!m_commandQueue.isEmpty()) runCommandQueue(); else { qDebug () << "parsing output: " << m_finalOutput << endl; m_currentExpression->parseOutput(m_finalOutput); m_finalOutput.clear(); } } } void QalculateSession::storeVariables(QString& currentCmd, QString output) { // internally we pass save(value,variable) command to qlac to save the variables. see parseSaveCommand() // TODO: if the user if trying to override a default variable(constants etc) or an existing variable, ask the user if he/she wants to override it or not. qDebug() << "save command " << currentCmd << endl; /** if we have reached here, we expect our variable model to be updated with new variables. In case the variable model is not updated, it most probably because we were not able to successfully parse the current command and output to extract variable and value This is probably not the best way to get the variable and value. But since qalc does not provide a way to get the list of variables, we will have to stick to parsing **/ QString value; QString var; QRegExp regex; // find the value regex.setPattern(QLatin1String("[\\s\\w\\W]+=\\s*([\\w\\W]+)")); if(regex.exactMatch(output)) { int pos = regex.indexIn(output); if (pos > -1) { value = regex.cap(1); value = value.trimmed(); value.replace(QLatin1String("\n"), QLatin1String("")); value.remove(QLatin1String(">")); } } //find the varaiable. // ex1: currentCmd = save(10, var_1,category, title): var_1 = variable // ex2: currentCmd = save(planet(jupter,mass), jupiter_mass, category, title): jupiter_mass = variable // Not the best regex. Cab be improved regex.setPattern(QLatin1String("\\s*save\\s*\\(\\s*[\\s\\w]+\\s*,([\\s\\w]+),*[\\w\\W]*\\)\\s*;*$|\\s*save\\s*\\(\\s*[\\s\\w\\W]+\\)\\s*,([\\s\\w]+),*[\\w\\W]*\\)\\s*;*$")); if(regex.exactMatch(currentCmd)) { int pos = regex.indexIn(currentCmd); if (pos > -1) { if(!regex.cap(1).trimmed().isEmpty()) var = regex.cap(1).trimmed(); else var = regex.cap(2).trimmed(); var = var.trimmed(); var.replace(QLatin1String("\n"), QLatin1String("")); var.remove(QLatin1String(">")); } } if(!value.isEmpty() && !var.isEmpty()) variables.insert(var, value); } void QalculateSession::readError() { QString error = QLatin1String(m_process->readAllStandardError()); if(m_currentExpression) { m_currentExpression->parseError(error); } } void QalculateSession::processStarted() { qDebug() << "process started " << m_process->program() << m_process->processId() << endl; } void QalculateSession::logout() { qDebug () << "logging out " << endl; if(m_process) { m_process->write("quit\n"); if(!m_process->waitForFinished(1000)) m_process->kill(); } changeStatus(Status::Disable); } void QalculateSession::interrupt() { qDebug () << "interrupting .... " << endl; if(m_currentExpression) m_currentExpression->interrupt(); m_commandQueue.clear(); m_expressionQueue.clear(); m_output.clear(); m_finalOutput.clear(); m_currentCommand.clear(); m_currentExpression = nullptr; } void QalculateSession::runExpression() { const QString& command = m_currentExpression->command(); foreach(const QString& cmd, command.split(QLatin1Char('\n'))) { m_commandQueue.enqueue(cmd); } runCommandQueue(); } void QalculateSession::runCommandQueue() { if (!m_commandQueue.isEmpty()) { m_currentCommand = m_commandQueue.dequeue(); // parse the current command if it's a save/load/store command if( m_currentCommand.toLower().trimmed().startsWith(QLatin1String("save")) || m_currentCommand.toLower().trimmed().startsWith(QLatin1String("store")) || m_currentCommand.trimmed().startsWith(QLatin1String("saveVariables"))) { m_currentCommand = parseSaveCommand(m_currentCommand); } m_currentCommand = m_currentCommand.trimmed(); m_currentCommand += QLatin1String("\n"); m_process->write(m_currentCommand.toLocal8Bit()); } } QString QalculateSession::parseSaveCommand(QString& currentCmd) { /* make sure the command is: * formatted correctly. e.g if the command is save(value,variable), we have to make sure that there is no space between save and '(', otherwise qalc waits for user input which is not supported by us as of now * supported save commands: save(value,variable,[category],[title]), save definitions, save mode, save var, store var, saveVariables filename */ QRegExp regex; regex.setCaseSensitivity(Qt::CaseInsensitive); regex.setPattern(QLatin1String("\\s*save\\s*definitions\\s*")); if(regex.exactMatch(currentCmd)) { // save the variables in ~/.cantor/backends/qalculate/definitions currentCmd.clear(); return currentCmd; } regex.setPattern(QLatin1String("\\s*save\\s*mode\\s*")); if(regex.exactMatch(currentCmd)) { // save the mode in ~/.cantor/backends/qalculate/cantor_qalc.cfg currentCmd.clear(); return currentCmd; } regex.setPattern(QLatin1String("\\s*saveVariables\\s*[\\w\\W]+")); if(regex.exactMatch(currentCmd)) { // save the variables in a file currentCmd.clear(); return currentCmd; } regex.setPattern(QLatin1String("\\s*store\\s*([a-zA-Z_]+[\\w]*)|\\s*save\\s*([a-zA-Z_]+[\\w]*)")); if(regex.exactMatch(currentCmd)) { m_isSaveCommand = true; int pos = regex.indexIn(currentCmd); if(pos > -1) { if(!regex.cap(1).trimmed().isEmpty()) currentCmd = QStringLiteral("save(%1, %2)").arg(QStringLiteral("ans")).arg(regex.cap(1).trimmed()); else currentCmd = QStringLiteral("save(%1, %2)").arg(QStringLiteral("ans")).arg(regex.cap(2).trimmed()); return currentCmd; } } regex.setPattern(QLatin1String("\\s*save\\s*(\\([\\w\\W]+\\))\\s*;*$")); if(regex.exactMatch(currentCmd)) { m_isSaveCommand = true; int pos = regex.indexIn(currentCmd); if (pos > -1) { currentCmd = QStringLiteral("save%1").arg(regex.cap(1).trimmed()); return currentCmd; } } /* If we have not returned by this point, it's because: * we did not parse the save command properly. This might be due to malformed regular expressions. * or the commnad given by the user is malformed. More likely to happen In both these cases we will simply return an empty string because we don't want qalc to run malformed queries, else it would wait for user input and hence Qprocess would never return a complete output and the expression will remain in 'calculating' state */ m_saveError = currentCmd + QLatin1String("\nError: Could not save.\n"); return QLatin1String(""); } void QalculateSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { // depending on the status of the expression change the status of the session; switch (status) { case Cantor::Expression::Computing: break; case Cantor::Expression::Interrupted: changeStatus(Cantor::Session::Done); break; case Cantor::Expression::Done: case Cantor::Expression::Error: qDebug() << " ****** STATUS " << status; changeStatus(Cantor::Session::Done); if(m_expressionQueue.size() > 0) m_expressionQueue.dequeue(); if(!m_expressionQueue.isEmpty()) runExpressionQueue(); } } Cantor::Expression* QalculateSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << " ** evaluating expression: " << cmd << endl; qDebug() << " size of expression queue: " << m_expressionQueue.size() << endl; changeStatus(Cantor::Session::Running); QalculateExpression* expr = new QalculateExpression(this, internal); expr->setFinishingBehavior(behave); expr->setCommand(cmd); m_expressionQueue.enqueue(expr); runExpressionQueue(); return expr; } void QalculateSession::runExpressionQueue() { if(!m_expressionQueue.isEmpty()) { if(!m_currentExpression) m_currentExpression = m_expressionQueue.head(); else { /* there was some expression that was being executed by cantor. We run the new expression only if the current expression's status is 'Done' or 'Error', if not , we simply return */ Cantor::Expression::Status expr_status = m_currentExpression->status(); if(expr_status != Cantor::Expression::Done && expr_status != Cantor::Expression::Error) return; } m_currentExpression = m_expressionQueue.head(); connect(m_currentExpression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); // start processing the expression m_currentExpression->evaluate(); } } Cantor::CompletionObject* QalculateSession::completionFor(const QString& command, int index) { return new QalculateCompletionObject(command, index, this); } Cantor::SyntaxHelpObject* QalculateSession::syntaxHelpFor(const QString& cmd) { return new QalculateSyntaxHelpObject(cmd, this); } QSyntaxHighlighter* QalculateSession::syntaxHighlighter(QObject* parent) { return new QalculateHighlighter(parent); } -QAbstractItemModel* QalculateSession::variableModel() +Cantor::DefaultVariableModel* QalculateSession::variableModel() const { return m_variableModel; } diff --git a/src/backends/qalculate/qalculatesession.h b/src/backends/qalculate/qalculatesession.h index 6d52b707..73109fa7 100644 --- a/src/backends/qalculate/qalculatesession.h +++ b/src/backends/qalculate/qalculatesession.h @@ -1,90 +1,90 @@ /************************************************************************************* * Copyright (C) 2009 by Milian Wolff * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef QALCULATE_SESSION_H #define QALCULATE_SESSION_H #include "session.h" #include "qalculateexpression.h" #include #include #include #include #include namespace Cantor { class DefaultVariableModel; } class QalculateEngine; class QProcess; class QalculateSession : public Cantor::Session { Q_OBJECT private: Cantor::DefaultVariableModel* m_variableModel; QProcess* m_process; QalculateExpression* m_currentExpression; QString m_output; QString m_finalOutput; QString m_currentCommand; QString m_saveError; QQueue m_expressionQueue; QQueue m_commandQueue; bool m_isSaveCommand; private: void runExpressionQueue(); void runCommandQueue(); QString parseSaveCommand(QString& currentCmd); void storeVariables(QString& currentCmd, QString output); public: explicit QalculateSession( Cantor::Backend* backend); ~QalculateSession() override; void login() override; void logout() override; void interrupt() override; Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; Cantor::CompletionObject* completionFor(const QString& cmd, int index=-1) override; Cantor::SyntaxHelpObject* syntaxHelpFor(const QString& cmd) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; void runExpression(); - QAbstractItemModel* variableModel() override; + Cantor::DefaultVariableModel* variableModel() const override; public: QMap variables; public Q_SLOTS: void readOutput(); void readError(); void processStarted(); void currentExpressionStatusChanged(Cantor::Expression::Status status); }; #endif diff --git a/src/backends/sage/sagesession.cpp b/src/backends/sage/sagesession.cpp index 6c8447c2..dab63d3a 100644 --- a/src/backends/sage/sagesession.cpp +++ b/src/backends/sage/sagesession.cpp @@ -1,506 +1,502 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "sagesession.h" #include "sageexpression.h" #include "sagecompletionobject.h" #include "sagehighlighter.h" #include #include #include #include #include #include #include "settings.h" #ifndef Q_OS_WIN #include #endif const QByteArray SageSession::SagePrompt="sage: "; //Text, sage outputs after each command const QByteArray SageSession::SageAlternativePrompt="....: "; //Text, sage outputs when it expects further input //some commands that are run after login static QByteArray initCmd="os.environ['PAGER'] = 'cat' \n "\ "sage.misc.pager.EMBEDDED_MODE = True \n "\ "sage.misc.viewer.BROWSER='' \n "\ "sage.misc.viewer.viewer.png_viewer('false') \n" \ "sage.plot.plot3d.base.SHOW_DEFAULTS['viewer'] = 'tachyon' \n"\ "sage.misc.latex.EMBEDDED_MODE = True \n "\ "os.environ['PAGER'] = 'cat' \n "\ "%colors nocolor \n "\ "print '____TMP_DIR____', sage.misc.misc.SAGE_TMP\n"; static QByteArray newInitCmd= "__CANTOR_IPYTHON_SHELL__=get_ipython() \n "\ "__CANTOR_IPYTHON_SHELL__.autoindent=False\n "; static QByteArray legacyInitCmd= "__CANTOR_IPYTHON_SHELL__=__IPYTHON__ \n " \ "__CANTOR_IPYTHON_SHELL__.autoindent=False\n "; static QByteArray endOfInitMarker="print '____END_OF_INIT____'\n "; SageSession::VersionInfo::VersionInfo(int major, int minor) { m_major=major; m_minor=minor; } int SageSession::VersionInfo::majorVersion() const { return m_major; } int SageSession::VersionInfo::minorVersion() const { return m_minor; } bool SageSession::VersionInfo::operator==(VersionInfo other) const { return m_major==other.m_major&&m_minor==other.m_minor; } bool SageSession::VersionInfo::operator<(VersionInfo other) const { return (m_major!= -1 && other.m_major==-1) || ( ((m_major!=-1 && other.m_major!=-1) || (m_major==other.m_major && m_major==-1) ) && ( m_major(SageSession::VersionInfo other) const { return !( (*this <= other )); } bool SageSession::VersionInfo::operator>=(SageSession::VersionInfo other) const { return !( *this < other); } SageSession::SageSession(Cantor::Backend* backend) : Session(backend), m_process(nullptr), m_isInitialized(false), m_waitingForPrompt(false), m_haveSentInitCmd(false) { connect( &m_dirWatch, SIGNAL(created(QString)), this, SLOT(fileCreated(QString)) ); } void SageSession::login() { qDebug()<<"login"; emit loginStarted(); m_process=new KPtyProcess(this); updateSageVersion(); const QString& sageExecFile = SageSettings::self()->path().toLocalFile(); if (false) // Reenable when https://trac.sagemath.org/ticket/25363 is merged // if (m_sageVersion >= SageSession::VersionInfo(8, 4)) m_process->setProgram(sageExecFile, QStringList() << QLatin1String("--simple-prompt")); else { const QString& sageStartScript = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/sagebackend/cantor-execsage")); m_process->setProgram(sageStartScript, QStringList(sageExecFile)); } m_process->setOutputChannelMode(KProcess::SeparateChannels); m_process->setPtyChannels(KPtyProcess::AllChannels); m_process->pty()->setEcho(false); connect(m_process->pty(), SIGNAL(readyRead()), this, SLOT(readStdOut())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus))); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); m_process->start(); m_process->waitForStarted(); m_process->pty()->write(initCmd); if(!SageSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = SageSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, SageExpression::DeleteOnFinish, true); } changeStatus(Session::Done); emit loginDone(); } void SageSession::logout() { qDebug()<<"logout"; disconnect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus))); m_process->pty()->write("exit\n"); m_process->kill(); //Run sage-cleaner to kill all the orphans KProcess::startDetached(SageSettings::self()->path().toLocalFile(),QStringList()<setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void SageSession::readStdOut() { m_outputCache.append(QString::fromUtf8(m_process->pty()->readAll())); qDebug()<<"out: "<= SageSession::VersionInfo(7,4)) { const QString message = i18n( "Sage version %1.%2 is unsupported. Please update your installation "\ "to the supported versions to make it work with Cantor.", m_sageVersion.majorVersion(), m_sageVersion.minorVersion()); KMessageBox::error(nullptr, message, i18n("Cantor")); interrupt(); logout(); } else if(m_sageVersion<=SageSession::VersionInfo(5, 7)) { qDebug()<<"using an old version of sage: "<pty()->write(legacyInitCmd); defineCustomFunctions(); m_process->pty()->write(endOfInitMarker); m_haveSentInitCmd=true; } } else { qDebug()<<"using the current set of commands"; if(!m_haveSentInitCmd) { m_process->pty()->write(newInitCmd); defineCustomFunctions(); m_process->pty()->write(endOfInitMarker); m_haveSentInitCmd=true; } } } else { const QString message = i18n( "Failed to determine the version of Sage. Please check your installation and the output of 'sage -v'."); KMessageBox::error(nullptr, message, i18n("Cantor")); interrupt(); logout(); } } int indexOfEOI=m_outputCache.indexOf(QLatin1String("____END_OF_INIT____")); if(indexOfEOI!=-1&&m_outputCache.indexOf(QLatin1String(SagePrompt), indexOfEOI)!=-1) { qDebug()<<"initialized"; //out.remove("____END_OF_INIT____"); //out.remove(SagePrompt); m_isInitialized=true; m_waitingForPrompt=false; runFirstExpression(); changeStatus(Cantor::Session::Done); m_outputCache.clear(); } //If we are waiting for another prompt, drop every output //until a prompt is found if(m_isInitialized&&m_waitingForPrompt) { qDebug()<<"waiting for prompt"; if(m_outputCache.contains(QLatin1String(SagePrompt))) m_waitingForPrompt=false; m_outputCache.clear(); return; } if(m_isInitialized) { if (!expressionQueue().isEmpty()) { SageExpression* expr = static_cast(expressionQueue().first()); expr->parseOutput(m_outputCache); } m_outputCache.clear(); } } void SageSession::readStdErr() { qDebug()<<"reading stdErr"; QString out=QLatin1String(m_process->readAllStandardError()); qDebug()<<"err: "<(expressionQueue().first()); expr->parseError(out); } } void SageSession::currentExpressionChangedStatus(Cantor::Expression::Status status) { switch (status) { case Cantor::Expression::Done: case Cantor::Expression::Error: - expressionQueue().removeFirst(); - if (expressionQueue().isEmpty()) - changeStatus(Done); - else - runFirstExpression(); - break; + finishFirstExpression(); + default: break; } } void SageSession::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); if(exitStatus==QProcess::CrashExit) { if(!expressionQueue().isEmpty()) { static_cast(expressionQueue().last()) ->onProcessError(i18n("The Sage process crashed while evaluating this expression")); }else { //We don't have an actual command. it crashed for some other reason, just show a plain error message box KMessageBox::error(nullptr, i18n("The Sage process crashed"), i18n("Cantor")); } }else { if(!expressionQueue().isEmpty()) { static_cast(expressionQueue().last()) ->onProcessError(i18n("The Sage process exited while evaluating this expression")); }else { //We don't have an actual command. it crashed for some other reason, just show a plain error message box KMessageBox::error(nullptr, i18n("The Sage process exited"), i18n("Cantor")); } } } void SageSession::reportProcessError(QProcess::ProcessError e) { if(e==QProcess::FailedToStart) { changeStatus(Cantor::Session::Done); emit error(i18n("Failed to start Sage")); } } void SageSession::runFirstExpression() { if(!expressionQueue().isEmpty()) { SageExpression* expr = static_cast(expressionQueue().first()); if (m_isInitialized) { connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); QString command=expr->command(); if(command.endsWith(QLatin1Char('?')) && !command.endsWith(QLatin1String("??"))) command=QLatin1String("help(")+command.left(command.size()-1)+QLatin1Char(')'); if(command.startsWith(QLatin1Char('?'))) command=QLatin1String("help(")+command.mid(1)+QLatin1Char(')'); command.append(QLatin1String("\n\n")); qDebug()<<"writing "<setStatus(Cantor::Expression::Computing); m_process->pty()->write(command.toUtf8()); } else if (expressionQueue().size() == 1) // If queue contains one expression, it means, what we run this expression immediately (drop setting queued status) // But we can't run expression before initializing, so mark expression, as queue expr->setStatus(Cantor::Expression::Queued); } } void SageSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); m_outputCache.clear(); } void SageSession::sendInputToProcess(const QString& input) { m_process->pty()->write(input.toUtf8()); } void SageSession::fileCreated( const QString& path ) { qDebug()<<"got a file "<(expressionQueue().first()); if ( expr ) expr->addFileResult( path ); } } void SageSession::setTypesettingEnabled(bool enable) { Cantor::Session::setTypesettingEnabled(enable); // We have problems with Sage latex output (generates invalid code sometimes), so disable sage // latex output until this not be solved. Users can enable sage latex by hands using %display // sage magic. //tell the sage server to enable/disable pretty_print //const QString cmd=QLatin1String("__cantor_enable_typesetting(%1)"); //evaluateExpression(cmd.arg(enable ? QLatin1String("true"):QLatin1String("false")), Cantor::Expression::DeleteOnFinish); } void SageSession::setWorksheetPath(const QString& path) { //save the path to the worksheet as variable "__file__" //this variable is usually set by the "os" package when running a script //but when it is run in an interpreter (like sage server) it is not set const QString cmd = QLatin1String("__file__ = '%1'"); evaluateExpression(cmd.arg(path), Cantor::Expression::DeleteOnFinish, true); } Cantor::CompletionObject* SageSession::completionFor(const QString& command, int index) { return new SageCompletionObject(command, index, this); } QSyntaxHighlighter* SageSession::syntaxHighlighter(QObject* parent) { return new SageHighlighter(parent); } SageSession::VersionInfo SageSession::sageVersion() { return m_sageVersion; } void SageSession::defineCustomFunctions() { //typesetting QString cmd=QLatin1String("def __cantor_enable_typesetting(enable):\n"); if(m_sageVersion VersionInfo(5, 7) && m_sageVersion< VersionInfo(5, 12)) { cmd+=QLatin1String("\t sage.misc.latex.pretty_print_default(enable)\n\n"); }else { cmd+=QLatin1String("\t if(enable==true):\n "\ "\t \t %display typeset \n"\ "\t else: \n" \ "\t \t %display simple \n\n"); } sendInputToProcess(cmd); } bool SageSession::updateSageVersion() { QProcess get_sage_version; get_sage_version.setProgram(SageSettings::self()->path().toLocalFile()); get_sage_version.setArguments(QStringList()< */ #include "scilabsession.h" #include "scilabexpression.h" #include "scilabhighlighter.h" #include "scilabcompletionobject.h" #include #include #include #include #include #include #include #include #include #include #include ScilabSession::ScilabSession( Cantor::Backend* backend) : Session(backend), m_process(nullptr), m_watch(nullptr), m_variableModel(new Cantor::DefaultVariableModel(this)) { } ScilabSession::~ScilabSession() { if (m_process) m_process->terminate(); } void ScilabSession::login() { qDebug()<<"login"; emit loginStarted(); QStringList args; args << QLatin1String("-nb"); m_process = new QProcess(this); m_process->setArguments(args); m_process->setProgram(ScilabSettings::self()->path().toLocalFile()); qDebug() << m_process->program(); m_process->setProcessChannelMode(QProcess::SeparateChannels); m_process->start(); m_process->waitForStarted(); m_process->waitForReadyRead(); if(ScilabSettings::integratePlots()){ qDebug() << "integratePlots"; QString tempPath = QDir::tempPath(); QString pathScilabOperations = tempPath; pathScilabOperations.prepend(QLatin1String("chdir('")); pathScilabOperations.append(QLatin1String("');\n")); qDebug() << "Processing command to change chdir in Scilab. Command " << pathScilabOperations.toLocal8Bit(); m_process->write(pathScilabOperations.toLocal8Bit()); m_watch = new KDirWatch(this); m_watch->setObjectName(QLatin1String("ScilabDirWatch")); m_watch->addDir(tempPath, KDirWatch::WatchFiles); qDebug() << "addDir " << tempPath << "? " << m_watch->contains(QLatin1String(tempPath.toLocal8Bit())); QObject::connect(m_watch, &KDirWatch::created, this, &ScilabSession::plotFileChanged); } if(!ScilabSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = ScilabSettings::self()->autorunScripts().join(QLatin1String("\n")); m_process->write(autorunScripts.toLocal8Bit()); } QObject::connect(m_process, &QProcess::readyReadStandardOutput, this, &ScilabSession::readOutput); QObject::connect(m_process, &QProcess::readyReadStandardError, this, &ScilabSession::readError); m_process->readAllStandardOutput().clear(); m_process->readAllStandardError().clear(); changeStatus(Cantor::Session::Done); emit loginDone(); } void ScilabSession::logout() { qDebug()<<"logout"; m_process->write("exit\n"); expressionQueue().clear(); m_variableModel->clearVariables(); QDir removePlotFigures; QListIterator i(m_listPlotName); while(i.hasNext()){ removePlotFigures.remove(QLatin1String(i.next().toLocal8Bit().constData())); } changeStatus(Status::Disable); } void ScilabSession::interrupt() { qDebug()<<"interrupt"; if (status() == Cantor::Session::Running) expressionQueue().first()->interrupt(); changeStatus(Cantor::Session::Done); } Cantor::Expression* ScilabSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; ScilabExpression* expr = new ScilabExpression(this, internal); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void ScilabSession::runFirstExpression() { qDebug() <<"call runFirstExpression"; qDebug() << "m_process: " << m_process; qDebug() << "status: " << (status() == Cantor::Session::Running ? "Running" : "Done"); if (!m_process) return; qDebug()<<"running next expression"; if(!expressionQueue().isEmpty()) { ScilabExpression* expr = static_cast(expressionQueue().first()); QString command; command.prepend(QLatin1String("\nprintf('begin-cantor-scilab-command-processing')\n")); command += expr->command(); command += QLatin1String("\nprintf('terminated-cantor-scilab-command-processing')\n"); connect(expr, &ScilabExpression::statusChanged, this, &ScilabSession::currentExpressionStatusChanged); expr->setStatus(Cantor::Expression::Computing); qDebug() << "Writing command to process" << command; m_process->write(command.toLocal8Bit()); } } void ScilabSession::readError() { qDebug() << "readError"; QString error = QLatin1String(m_process->readAllStandardError()); qDebug() << "error: " << error; static_cast(expressionQueue().first())->parseError(error); } void ScilabSession::readOutput() { qDebug() << "readOutput"; while(m_process->bytesAvailable() > 0){ m_output.append(QString::fromLocal8Bit(m_process->readLine())); } qDebug() << "output.isNull? " << m_output.isNull(); qDebug() << "output: " << m_output; if(status() != Running || m_output.isNull()){ return; } if(m_output.contains(QLatin1String("begin-cantor-scilab-command-processing")) && m_output.contains(QLatin1String("terminated-cantor-scilab-command-processing"))){ m_output.remove(QLatin1String("begin-cantor-scilab-command-processing")); m_output.remove(QLatin1String("terminated-cantor-scilab-command-processing")); static_cast(expressionQueue().first())->parseOutput(m_output); m_output.clear(); } } void ScilabSession::plotFileChanged(const QString& filename) { qDebug() << "plotFileChanged filename:" << filename; if (expressionQueue().first() && (filename.contains(QLatin1String("cantor-export-scilab-figure")))){ qDebug() << "Calling parsePlotFile"; static_cast(expressionQueue().first())->parsePlotFile(filename); m_listPlotName.append(filename); } } void ScilabSession::currentExpressionStatusChanged(Cantor::Expression::Status status) { qDebug() << "currentExpressionStatusChanged: " << status; switch (status){ case Cantor::Expression::Computing: case Cantor::Expression::Interrupted: case Cantor::Expression::Queued: break; case Cantor::Expression::Done: case Cantor::Expression::Error: expressionQueue().removeFirst(); if (expressionQueue().isEmpty()) changeStatus(Done); else runFirstExpression(); break; } } QSyntaxHighlighter* ScilabSession::syntaxHighlighter(QObject* parent) { ScilabHighlighter *highlighter = new ScilabHighlighter(parent, this); return highlighter; } Cantor::CompletionObject* ScilabSession::completionFor(const QString& command, int index) { return new ScilabCompletionObject(command, index, this); } -QAbstractItemModel* ScilabSession::variableModel() +Cantor::DefaultVariableModel* ScilabSession::variableModel() const { return m_variableModel; } diff --git a/src/backends/scilab/scilabsession.h b/src/backends/scilab/scilabsession.h index 250ca2ee..2c4f4bd2 100644 --- a/src/backends/scilab/scilabsession.h +++ b/src/backends/scilab/scilabsession.h @@ -1,74 +1,74 @@ /* 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) 2011 Filipe Saraiva */ #ifndef _SCILABSESSION_H #define _SCILABSESSION_H #include "session.h" #include "scilabexpression.h" #include #include namespace Cantor { class DefaultVariableModel; } class ScilabExpression; class KDirWatch; class QProcess; class ScilabSession : public Cantor::Session { Q_OBJECT public: explicit ScilabSession(Cantor::Backend* backend); ~ScilabSession() override; void login() override; void logout() override; void interrupt() override; void runExpression(ScilabExpression* expr); QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; 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; void runFirstExpression() override; - QAbstractItemModel* variableModel() override; + Cantor::DefaultVariableModel* variableModel() const override; public Q_SLOTS: void readOutput(); void readError(); void plotFileChanged(const QString& filename); private: QProcess* m_process; KDirWatch* m_watch; QStringList m_listPlotName; QString m_output; Cantor::DefaultVariableModel* m_variableModel; private Q_SLOTS: void currentExpressionStatusChanged(Cantor::Expression::Status status); }; #endif /* _SCILABSESSION_H */ diff --git a/src/lib/defaulthighlighter.cpp b/src/lib/defaulthighlighter.cpp index 5792cd26..877bd5a7 100644 --- a/src/lib/defaulthighlighter.cpp +++ b/src/lib/defaulthighlighter.cpp @@ -1,460 +1,482 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2006 David Saxton */ #include "defaulthighlighter.h" +#include "defaultvariablemodel.h" +#include "session.h" + #include #include #include #include #include #include #include #include using namespace Cantor; struct HighlightingRule { QRegExp regExp; QTextCharFormat format; }; bool operator==(const HighlightingRule& rule1, const HighlightingRule& rule2) { return rule1.regExp == rule2.regExp; } struct PairOpener { PairOpener() : position(-1), type(-1) { } PairOpener(int p, int t) : position(p), type(t) { } int position; int type; }; class Cantor::DefaultHighlighterPrivate { public: QTextCursor cursor; //Character formats to use for the highlighting QTextCharFormat functionFormat; QTextCharFormat variableFormat; QTextCharFormat objectFormat; QTextCharFormat keywordFormat; QTextCharFormat numberFormat; QTextCharFormat operatorFormat; QTextCharFormat errorFormat; QTextCharFormat commentFormat; QTextCharFormat stringFormat; QTextCharFormat matchingPairFormat; QTextCharFormat mismatchingPairFormat; int lastBlockNumber; int lastPosition; bool suppressRuleChangedSignal; // each two consecutive items build a pair QList pairs; QList regExpRules; QHash wordRules; }; DefaultHighlighter::DefaultHighlighter(QObject* parent) - : QSyntaxHighlighter(parent), - d(new DefaultHighlighterPrivate) + : QSyntaxHighlighter(parent), + d(new DefaultHighlighterPrivate) { d->cursor = QTextCursor(); d->lastBlockNumber=-1; d->lastPosition=-1; d->suppressRuleChangedSignal = false; addPair(QLatin1Char('('), QLatin1Char(')')); addPair(QLatin1Char('['), QLatin1Char(']')); addPair(QLatin1Char('{'), QLatin1Char('}')); updateFormats(); connect(qApp, &QGuiApplication::paletteChanged, this, &DefaultHighlighter::updateFormats); } +DefaultHighlighter::DefaultHighlighter(QObject* parent, Session* session) + :DefaultHighlighter(parent) +{ + if (session) + { + DefaultVariableModel* model = session->variableModel(); + if (model) + { + connect(model, &DefaultVariableModel::variablesAdded, this, &DefaultHighlighter::addVariables); + connect(model, &DefaultVariableModel::variablesRemoved, this, &DefaultHighlighter::removeRules); + connect(model, &DefaultVariableModel::functionsAdded, this, &DefaultHighlighter::addFunctions); + connect(model, &DefaultVariableModel::functionsRemoved, this, &DefaultHighlighter::removeRules); + + addVariables(model->variableNames()); + addFunctions(model->functions()); + } + } +} + DefaultHighlighter::~DefaultHighlighter() { delete d; } void DefaultHighlighter::setTextItem(QGraphicsTextItem* item) { d->cursor = item->textCursor(); setDocument(item->document()); // make sure every item is connected only once item->disconnect(this, SLOT(positionChanged(QTextCursor))); // QGraphicsTextItem has no signal cursorPositionChanged, but item really // is a WorksheetTextItem connect(item, SIGNAL(cursorPositionChanged(QTextCursor)), this, SLOT(positionChanged(QTextCursor))); d->lastBlockNumber = -1; d->lastPosition = -1; } bool DefaultHighlighter::skipHighlighting(const QString& text) { return text.isEmpty(); } void DefaultHighlighter::highlightBlock(const QString& text) { //qDebug() << text; const QTextCursor& cursor = d->cursor; d->lastBlockNumber = cursor.blockNumber(); if (skipHighlighting(text)) return; highlightPairs(text); highlightWords(text); highlightRegExps(text); } void DefaultHighlighter::addPair(QChar openSymbol, QChar closeSymbol) { Q_ASSERT(!d->pairs.contains(openSymbol)); Q_ASSERT(!d->pairs.contains(closeSymbol)); d->pairs << openSymbol << closeSymbol; } void DefaultHighlighter::highlightPairs(const QString& text) { //qDebug() << text; const QTextCursor& cursor = d->cursor; int cursorPos = -1; if (cursor.blockNumber() == currentBlock().blockNumber() ) { cursorPos = cursor.position() - currentBlock().position(); // when text changes, this will be called before the positionChanged signal // gets emitted. Hence update the position so we don't highlight twice d->lastPosition = cursor.position(); } QStack opened; for (int i = 0; i < text.size(); ++i) { int idx = d->pairs.indexOf(text[i]); if (idx == -1) continue; if (idx % 2 == 0) { //opener of a pair opened.push(PairOpener(i, idx)); } else if (opened.isEmpty()) { //closer with no previous opener setFormat(i, 1, errorFormat()); } else if (opened.top().type == idx - 1) { //closer with matched opener int openPos = opened.pop().position; if (cursorPos != -1 && (openPos == cursorPos || openPos == cursorPos - 1 || i == cursorPos || i == cursorPos - 1)) { setFormat(openPos, 1, matchingPairFormat()); setFormat(i, 1, matchingPairFormat()); } } else { //closer with mismatching opener int openPos = opened.pop().position; setFormat(openPos, 1, mismatchingPairFormat()); setFormat(i, 1, mismatchingPairFormat()); } } // handled unterminated pairs while (!opened.isEmpty()) { int position = opened.pop().position; setFormat(position, 1, errorFormat()); } } void DefaultHighlighter::highlightWords(const QString& text) { //qDebug() << "DefaultHighlighter::highlightWords"; const QStringList& words = text.split(QRegExp(QLatin1String("\\b")), QString::SkipEmptyParts); int count; int pos = 0; const int n = words.size(); for (int i = 0; i < n; ++i) { count = words[i].size(); QString word = words[i]; //kind of a HACK: //look at previous words, if they end with allowed characters, //prepend them to the current word. This allows for example //to highlight words that start with a "Non-word"-character //e.g. %pi in the scilab backend. //qDebug() << "nonSeparatingCharacters().isNull(): " << nonSeparatingCharacters().isNull(); if(!nonSeparatingCharacters().isNull()) { for(int j = i - 1; j >= 0; j--) { //qDebug() << "j: " << j << "w: " << words[j]; const QString& w = words[j]; const QString exp = QStringLiteral("(%1)*$").arg(nonSeparatingCharacters()); //qDebug() << "exp: " << exp; int idx = w.indexOf(QRegExp(exp)); const QString& s = w.mid(idx); //qDebug() << "s: " << s; if(s.size() > 0) { pos -= s.size(); count += s.size(); word = s + word; } else{ break; } } } word = word.trimmed(); //qDebug() << "highlighing: " << word; if (d->wordRules.contains(word)) { setFormat(pos, count, d->wordRules[word]); } pos += count; } } void DefaultHighlighter::highlightRegExps(const QString& text) { foreach (const HighlightingRule& rule, d->regExpRules) { int index = rule.regExp.indexIn(text); while (index >= 0) { int length = rule.regExp.matchedLength(); setFormat(index, length, rule.format); index = rule.regExp.indexIn(text, index + length); } } } QTextCharFormat DefaultHighlighter::functionFormat() const { return d->functionFormat; } QTextCharFormat DefaultHighlighter::variableFormat() const { return d->variableFormat; } QTextCharFormat DefaultHighlighter::objectFormat() const { return d->objectFormat; } QTextCharFormat DefaultHighlighter::keywordFormat() const { return d->keywordFormat; } QTextCharFormat DefaultHighlighter::numberFormat() const { return d->numberFormat; } QTextCharFormat DefaultHighlighter::operatorFormat() const { return d->operatorFormat; } QTextCharFormat DefaultHighlighter::errorFormat() const { return d->errorFormat; } QTextCharFormat DefaultHighlighter::commentFormat() const { return d->commentFormat; } QTextCharFormat DefaultHighlighter::stringFormat() const { return d->stringFormat; } QTextCharFormat DefaultHighlighter::matchingPairFormat() const { return d->matchingPairFormat; } QTextCharFormat DefaultHighlighter::mismatchingPairFormat() const { return d->mismatchingPairFormat; } void DefaultHighlighter::updateFormats() { //initialize char-formats KColorScheme scheme(QPalette::Active); d->functionFormat.setForeground(scheme.foreground(KColorScheme::LinkText)); d->functionFormat.setFontWeight(QFont::DemiBold); d->variableFormat.setForeground(scheme.foreground(KColorScheme::ActiveText)); d->objectFormat.setForeground(scheme.foreground(KColorScheme::NormalText)); d->objectFormat.setFontWeight(QFont::Bold); d->keywordFormat.setForeground(scheme.foreground(KColorScheme::NeutralText)); d->keywordFormat.setFontWeight(QFont::Bold); d->numberFormat.setForeground(scheme.foreground(KColorScheme::NeutralText)); d->operatorFormat.setForeground(scheme.foreground(KColorScheme::NormalText)); d->operatorFormat.setFontWeight(QFont::Bold); d->errorFormat.setForeground(scheme.foreground(KColorScheme::NormalText)); d->errorFormat.setUnderlineColor(scheme.foreground(KColorScheme::NegativeText).color()); d->errorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); d->commentFormat.setForeground(scheme.foreground(KColorScheme::InactiveText)); d->stringFormat.setForeground(scheme.foreground(KColorScheme::PositiveText)); d->matchingPairFormat.setForeground(scheme.foreground(KColorScheme::NeutralText)); d->matchingPairFormat.setBackground(scheme.background(KColorScheme::NeutralBackground)); d->mismatchingPairFormat.setForeground(scheme.foreground(KColorScheme::NegativeText)); d->mismatchingPairFormat.setBackground(scheme.background(KColorScheme::NegativeBackground)); } void DefaultHighlighter::positionChanged(const QTextCursor& cursor) { if (!cursor.isNull() && cursor.document() != document()) // A new item notified us, but we did not yet change our document. // We are waiting for that to happen. return; d->cursor = cursor; if ( (cursor.isNull() || cursor.blockNumber() != d->lastBlockNumber) && d->lastBlockNumber >= 0 ) { // remove highlight from last focused block rehighlightBlock(document()->findBlockByNumber(d->lastBlockNumber)); } if (cursor.isNull()) { d->lastBlockNumber = -1; d->lastPosition = -1; return; } d->lastBlockNumber = cursor.blockNumber(); if ( d->lastPosition == cursor.position() ) { return; } rehighlightBlock(cursor.block()); d->lastPosition = cursor.position(); } void DefaultHighlighter::addRule(const QString& word, const QTextCharFormat& format) { d->wordRules[word] = format; if (!d->suppressRuleChangedSignal) emit rulesChanged(); } void DefaultHighlighter::addRule(const QRegExp& regexp, const QTextCharFormat& format) { HighlightingRule rule = { regexp, format }; d->regExpRules.removeAll(rule); d->regExpRules.append(rule); if (!d->suppressRuleChangedSignal) emit rulesChanged(); } void DefaultHighlighter::removeRule(const QString& word) { d->wordRules.remove(word); if (!d->suppressRuleChangedSignal) emit rulesChanged(); } void DefaultHighlighter::removeRule(const QRegExp& regexp) { HighlightingRule rule = { regexp, QTextCharFormat() }; d->regExpRules.removeAll(rule); if (!d->suppressRuleChangedSignal) emit rulesChanged(); } void DefaultHighlighter::addRules(const QStringList& conditions, const QTextCharFormat& format) { typename QStringList::const_iterator i = conditions.constBegin(); typename QStringList::const_iterator end = conditions.constEnd(); d->suppressRuleChangedSignal = true; for (;i != end; ++i) { addRule(*i, format); } d->suppressRuleChangedSignal = false; emit rulesChanged(); } void DefaultHighlighter::addFunctions(const QStringList& functions) { addRules(functions, functionFormat()); } void DefaultHighlighter::addKeywords(const QStringList& keywords) { addRules(keywords, keywordFormat()); } void DefaultHighlighter::addVariables(const QStringList& variables) { addRules(variables, variableFormat()); } void DefaultHighlighter::removeRules(const QStringList& conditions) { typename QStringList::const_iterator i = conditions.constBegin(); typename QStringList::const_iterator end = conditions.constEnd(); d->suppressRuleChangedSignal = true; for (;i != end; ++i) { removeRule(*i); } d->suppressRuleChangedSignal = false; emit rulesChanged(); } QString DefaultHighlighter::nonSeparatingCharacters() const { return QString(); } diff --git a/src/lib/defaulthighlighter.h b/src/lib/defaulthighlighter.h index 53b945c0..b5a9e565 100644 --- a/src/lib/defaulthighlighter.h +++ b/src/lib/defaulthighlighter.h @@ -1,184 +1,187 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef DEFAULTHIGHLIGHTER_H #define DEFAULTHIGHLIGHTER_H #include "cantor_export.h" #include class QGraphicsTextItem; namespace Cantor { class DefaultHighlighterPrivate; +class Session; /** * The DefaultHighlighter is an implementation QSyntaxHighlighter. * It covers most common cases of syntax highlighting for Cantor's command entries. * * When creating a custom highlighter, for example for a new backend, you should use * the provided functions addPairs(), addRule() and/or addRules(). * * If you need more specific functionality, subclass highlightBlock(). Usually it's a good idea to also call * DefaultHighlighter's implementation from it. * * @author Alexander Rieder */ class CANTOR_EXPORT DefaultHighlighter : public QSyntaxHighlighter { Q_OBJECT public: explicit DefaultHighlighter(QObject* parent); + explicit DefaultHighlighter(QObject* parent, Session* session); ~DefaultHighlighter() override; /** * Change the item being highlighted. */ void setTextItem(QGraphicsTextItem* item); public Q_SLOTS: /** * Called when the cursor moved. Rehighlights accordingly. */ void positionChanged(const QTextCursor&); + protected Q_SLOTS: + /** + * Convenience method, equivalent to @code addRules(functions, functionFormat()) @endcode + */ + void addFunctions(const QStringList& functions); + /** + * Convenience method, equivalent to @code addRules(variables, variableFormat()) @endcode + */ + void addVariables(const QStringList& variables); + /** + * Removes any rules previously added for the word @p word + */ + void removeRule(const QString& word); + /** + * Convenience method, removes all rules with conditions from @p conditions + * @sa removeRule, addRules + */ + void removeRules(const QStringList& conditions); + protected: /** * This method is called by Cantor's KTextEdit and is where all the highlighting must take place. * The default implementation calls highlightPairs(), highlightWords() and highlightRegExps(). * */ void highlightBlock(const QString& text) override; bool skipHighlighting(const QString& text); QTextCharFormat functionFormat() const; QTextCharFormat variableFormat() const; QTextCharFormat objectFormat() const; QTextCharFormat keywordFormat() const; QTextCharFormat numberFormat() const; QTextCharFormat operatorFormat() const; QTextCharFormat errorFormat() const; QTextCharFormat commentFormat() const; QTextCharFormat stringFormat() const; QTextCharFormat matchingPairFormat() const; QTextCharFormat mismatchingPairFormat() const; /** * Call this to add a pair of symbols for highlighting. * The default implementation of the class already adds (), {} and [], so no need to add those. * For example, if you wanted to highlight angle-brackets, you would use: * @code * addPair('<', '>'); * @endcode * @param openSymbol the opening symbol of the pair * @param closeSymbol the closing symbol of the pair * @sa highlightPairs */ void addPair(QChar openSymbol, QChar closeSymbol); /** * Highlights all instances of the @p word in the text with the specified @p format * @param word the word to highlight * @param format the format to be used for displaying the word */ void addRule(const QString& word, const QTextCharFormat& format); /** * Highlights all parts of the text matched by the regular expression @p regexp in the text * with the specified @p format * @param regexp the regular expression used to look for matches * @param format the format used to display the matching parts of the text */ void addRule(const QRegExp& regexp, const QTextCharFormat& format); /** * Convenience method, highlights all items in @p conditions with the specified @p format * @code * QStringList greenWords; * greenWords << "tree" << "forest" << "grass"; * addRules(greenWords, greenWordFormat); * @endcode * @param conditions any Qt container of QRegExp or QString. * @param format the format used to display the matching parts of the text */ void addRules(const QStringList& conditions, const QTextCharFormat& format); - /** - * Convenience method, equivalent to @code addRules(functions, functionFormat()) @endcode - */ - void addFunctions(const QStringList& functions); - /** - * Convenience method, equivalent to @code addRules(variables, variableFormat()) @endcode - */ - void addVariables(const QStringList& variables); /** * Convenience method, equivalent to @code addRules(keywords, keywordFormat()) @endcode */ void addKeywords(const QStringList& keywords); - - /** - * Removes any rules previously added for the word @p word - */ - void removeRule(const QString& word); /** * Removes any rules previously added for the regular expression @p regexp */ void removeRule(const QRegExp& regexp); - /** - * Convenience method, removes all rules with conditions from @p conditions - * @sa removeRule, addRules - */ - void removeRules(const QStringList& conditions); /** * Highlight pairs added with addPair() * @sa addPair */ void highlightPairs(const QString& text); /** * Highlights words added with addRule() * @sa addRule, addRules */ void highlightWords(const QString& text); /** * Highlights all matches from regular expressions added with addRule() * @sa addRule, addRules */ void highlightRegExps(const QString& text); /** * Returns a string that contains a regular expression that matches for characters thar are allowed inside * words for this backend. For example, maxima or scilab allow % at the beginning of variable names */ virtual QString nonSeparatingCharacters() const; private Q_SLOTS: void updateFormats(); Q_SIGNALS: void rulesChanged(); private: DefaultHighlighterPrivate* d; }; } #endif diff --git a/src/lib/defaultvariablemodel.cpp b/src/lib/defaultvariablemodel.cpp index e8837abb..b5f4e164 100644 --- a/src/lib/defaultvariablemodel.cpp +++ b/src/lib/defaultvariablemodel.cpp @@ -1,193 +1,338 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2010 Miha Čančula */ #include "defaultvariablemodel.h" #include #include #include "extension.h" #include "backend.h" namespace Cantor { class DefaultVariableModelPrivate { public: QList variables; + QStringList functions; Session* session; VariableManagementExtension* extension; }; DefaultVariableModel::DefaultVariableModel(Session* session): QAbstractTableModel(session), d_ptr(new DefaultVariableModelPrivate) { Q_D(DefaultVariableModel); d->session = session; if (session) { d->extension = dynamic_cast(session->backend()->extension(QStringLiteral("VariableManagementExtension"))); } qDebug() << d->session << d->extension; } int DefaultVariableModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return ColumnCount; } int DefaultVariableModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } else { Q_D(const DefaultVariableModel); return d->variables.size(); } } QVariant DefaultVariableModel::headerData(int section, Qt::Orientation orientation, int role) const { if(role==Qt::DisplayRole && orientation==Qt::Horizontal) { switch(section) { case NameColumn: return i18nc("@title:column", "Name"); case ValueColumn: return i18nc("@title:column", "Value"); break; } } return QVariant(); } Qt::ItemFlags DefaultVariableModel::flags(const QModelIndex& index) const { return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; } QVariant DefaultVariableModel::data(const QModelIndex& index, int role) const { if (role != Qt::DisplayRole || !index.isValid()) { return QVariant(); } Q_D(const DefaultVariableModel); switch (index.column()) { case NameColumn: return QVariant(d->variables[index.row()].name); case ValueColumn: return QVariant(d->variables[index.row()].value); default: return QVariant(); } } bool DefaultVariableModel::setData(const QModelIndex& index, const QVariant& value, int role) { if(role!=Qt::EditRole || !value.isValid() || !index.isValid()) { return false; } Q_D(const DefaultVariableModel); if(index.column() == ValueColumn) { // Changing values QString name = data(index.sibling(index.row(), NameColumn)).toString(); d->session->evaluateExpression(d->extension->setValue(name, value.toString()), Expression::DeleteOnFinish); return true; } else if(index.column() == NameColumn) { // Renaming => copy it, then delete the old one QString oldName = data(index).toString(); QString variableValue = data(index.sibling(index.row(), ValueColumn)).toString(); d->session->evaluateExpression(d->extension->addVariable(value.toString(), variableValue), Expression::DeleteOnFinish); d->session->evaluateExpression(d->extension->removeVariable(oldName), Expression::DeleteOnFinish); return true; } return false; } void DefaultVariableModel::addVariable(const QString& name, const QString& value) { Variable v; v.name = name; v.value = value; addVariable(v); } void DefaultVariableModel::addVariable(const Cantor::DefaultVariableModel::Variable& variable) { Q_D(DefaultVariableModel); if ( d->variables.contains(variable) ) { + // TODO: Why we remove variable here? Set value properly removeVariable(variable); } beginInsertRows(QModelIndex(), d->variables.size(), d->variables.size()); d->variables.append(variable); + emit variablesAdded(QStringList(variable.name)); endInsertRows(); } void DefaultVariableModel::removeVariable(const QString& name) { Variable v; v.name = name; removeVariable(v); } void DefaultVariableModel::removeVariable(const Cantor::DefaultVariableModel::Variable& variable) { Q_D(DefaultVariableModel); int row = d->variables.indexOf(variable); if(row==-1) return; + const QString& name = variable.name; beginRemoveRows(QModelIndex(), row, row); d->variables.removeAt(row); endRemoveRows(); + emit variablesRemoved(QStringList(name)); } void DefaultVariableModel::clearVariables() { Q_D(DefaultVariableModel); beginResetModel(); + + QStringList names; + for (const Variable var: d->variables) + names.append(var.name); + d->variables.clear(); endResetModel(); + + emit variablesRemoved(names); +} + +void DefaultVariableModel::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/defaultvariablemodel.h b/src/lib/defaultvariablemodel.h index cdbae980..fe86a496 100644 --- a/src/lib/defaultvariablemodel.h +++ b/src/lib/defaultvariablemodel.h @@ -1,143 +1,195 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2010 Miha Čančula */ #ifndef CANTOR_DEFAULTVARIABLEMODEL_H #define CANTOR_DEFAULTVARIABLEMODEL_H #include #include "session.h" #include "expression.h" namespace Cantor { class DefaultVariableModelPrivate; /** * @brief * This DefaultVariableModel class is an implementation of QAbstractItemModel * that can be used with the Variable Manager plugin. * * For most uses the addVariable(), removeVariable() and clearVariables() methods are sufficient. * They can be used from session (directly or by connecting signals to them), or called from * a subclass. * * DefaultVariableModel uses the session to run expressions for changing variables, and it * gets the commands from the backend's VariableManagementExtension. * If you do not want this behavior, you can subclass it and reimplement data() and/or setData(). * * @see Session::variableModel() */ class CANTOR_EXPORT DefaultVariableModel : public QAbstractTableModel { Q_OBJECT Q_PROPERTY(Session* session READ session) public: /** * A structure representing a variable. */ struct Variable { /** * The variable's name */ QString name; /** * The variable's value, represented as a string */ QString value; }; /** * Default constructor * If you are constructing a DefaultVariableModel without subclassing, the @p session must be valid * and its backends must support a VariableManagementExtension. * * This requirement can be avoided by reimplementing setData() in a subclass. * * @param session the session this Model belongs to, also becomes the Model's parent. */ explicit DefaultVariableModel(Session* session); ~DefaultVariableModel() override = default; /** * Get the session which created this Model and whose variables it contains * @return the session */ 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. * If a variable with the same name already exists, it will be overwritten. * @param name the name of the variable * @param value the value of the variable */ void addVariable(const QString& name, const QString& value); /** * Convenience method, equivalent to addVariable(variable.name, variable.value) * @param variable the variable to add */ void addVariable(const Cantor::DefaultVariableModel::Variable& variable); /** * Remove the variable @p name from the model. * If a variable with the specified @p name doesn't exists, this method does nothing. * @param name the name of the variable to remove */ void removeVariable(const QString& name); /** * Convenience method, equivalent to removeVariable(variable.name) * @param variable the variable to remove */ void removeVariable(const Cantor::DefaultVariableModel::Variable& variable); /** * Clears all variables from the model */ 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; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; 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, ValueColumn = 1, ColumnCount = 2 }; private: DefaultVariableModelPrivate* const d_ptr; Q_DECLARE_PRIVATE(DefaultVariableModel) }; bool operator==(const Cantor::DefaultVariableModel::Variable& one, const Cantor::DefaultVariableModel::Variable& other); } #endif // CANTOR_DEFAULTVARIABLEMODEL_H diff --git a/src/lib/session.cpp b/src/lib/session.cpp index 1088f8ad..95c0e344 100644 --- a/src/lib/session.cpp +++ b/src/lib/session.cpp @@ -1,147 +1,193 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "session.h" 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; } QList& Cantor::Session::expressionQueue() const { return d->expressionQueue; } void Session::enqueueExpression(Expression* expr) { d->expressionQueue.append(expr); //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); } void Session::runFirstExpression() { } +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; } Cantor::Session::Status Session::status() { return d->status; } void Session::changeStatus(Session::Status newStatus) { d->status=newStatus; emit statusChanged(newStatus); } void Session::setTypesettingEnabled(bool enable) { d->typesettingEnabled=enable; } bool Session::isTypesettingEnabled() { return d->typesettingEnabled; } void Session::setWorksheetPath(const QString& path) { Q_UNUSED(path); return; } CompletionObject* Session::completionFor(const QString& cmd, int index) { Q_UNUSED(cmd); Q_UNUSED(index); //Return 0 per default, so Backends not offering tab completions don't have //to reimplement this. This method should only be called on backends with //the Completion Capability flag return nullptr; } SyntaxHelpObject* Session::syntaxHelpFor(const QString& cmd) { Q_UNUSED(cmd); //Return 0 per default, so Backends not offering tab completions don't have //to reimplement this. This method should only be called on backends with //the SyntaxHelp Capability flag return nullptr; } QSyntaxHighlighter* Session::syntaxHighlighter(QObject* parent) { Q_UNUSED(parent); return nullptr; } -QAbstractItemModel* Session::variableModel() +DefaultVariableModel* Session::variableModel() const { - //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; +} + +QAbstractItemModel* Session::variableDataModel() const +{ + return variableModel(); +} + +void Session::updateVariables() +{ + if (d->variableModel) + { + d->variableModel->update(); + d->needUpdate = false; + } +} + +void Cantor::Session::setVariableModel(Cantor::DefaultVariableModel* model) +{ + d->variableModel = model; } int Session::nextExpressionId() { return d->expressionCount++; } diff --git a/src/lib/session.h b/src/lib/session.h index bae20b78..3d4e2743 100644 --- a/src/lib/session.h +++ b/src/lib/session.h @@ -1,221 +1,254 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #ifndef _SESSION_H #define _SESSION_H #include #include "cantor_export.h" #include "expression.h" class QTextEdit; class QSyntaxHighlighter; class QAbstractItemModel; /** * Namespace collecting all Classes of the Cantor Libraries */ namespace Cantor { class Backend; class SessionPrivate; class CompletionObject; class SyntaxHelpObject; +class DefaultVariableModel; /** * The Session object is the main class used to interact with a Backend. * It is used to evaluate Expressions, get completions, syntax highlighting, etc. * * @author Alexander Rieder */ class CANTOR_EXPORT Session : public QObject { Q_OBJECT 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 */ ~Session() override; /** * Login to the Session. In this function you should do anything needed to set up * the session, and make it ready for usage. The method should be implemented non-blocking. * Emit loginStarted() prior to connection to the actual backend in order to notify cantor_part about it. * If the logging in is completed, the loginDone() signal must be emitted */ virtual void login() = 0; /** * Log out of the Session. Destroy everything specific to a single session, e.g. * stop all the running processes etc. Also logout session status must be Status::Disable * NOTE: restarting the session consists of first logout() and then login() */ virtual void logout() = 0; /** * Passes the given command to the backend and returns a Pointer * to a new Expression object, which will emit the gotResult() * signal as soon as the computation is done. The result will * then be accessible by Expression::result() * @param command the command that should be run by the backend. * @param finishingBehavior the FinishingBehaviour that should be used for this command. @see Expression::FinishingBehaviour * @param internal true, if it is an internal command @see Expression::Expression(Session*, bool) * @return an Expression object, representing this command */ virtual Expression* evaluateExpression(const QString& command, Expression::FinishingBehavior finishingBehavior = Expression::FinishingBehavior::DoNotDelete, bool internal = false) = 0; /** * Append the expression to queue . * @see expressionQueue() const */ void enqueueExpression(Expression*); /** * Interrupts all the running calculations in this session */ virtual void interrupt() = 0; /** * Returns tab-completion, for this command/command-part. * The return type is a CompletionObject. The fetching * of the completions works asynchronously, you'll have to * listen to the done() Signal of the returned object * @param cmd The partial command that should be completed * @param index The index (cursor position) at which completion * was invoked. Defaults to -1, indicating the end of the string. * @return a Completion object, representing this completion * @see CompletionObject */ virtual CompletionObject* completionFor(const QString& cmd, int index = -1); /** * Returns Syntax help, for this command. * It returns a SyntaxHelpObject, that will fetch the * needed information asynchronously. You need to listen * to the done() Signal of the Object * @param cmd the command, syntax help is requested for * @return SyntaxHelpObject, representing the help request * @see SyntaxHelpObject */ virtual SyntaxHelpObject* syntaxHelpFor(const QString& cmd); /** * returns a syntax highlighter for this session * @param parent QObject the Highlighter's parent * @return QSyntaxHighlighter doing the highlighting for this Session */ virtual QSyntaxHighlighter* syntaxHighlighter(QObject* parent); /** - * returns a Model to interact with the variables - * @return QAbstractItemModel to interact with the variables + * returns a Model to interact with the variables or nullptr, if + * this backend have a variable model, which not inherit from + * default variable model class (in this case @see variableDataModel()) + * @return DefaultVariableModel to interact with the variables */ - virtual QAbstractItemModel* variableModel(); + virtual DefaultVariableModel* variableModel() const; + + /** + * returns QAbstractItemModel to interact with the variables + */ + virtual QAbstractItemModel* variableDataModel() const; /** * Enables/disables Typesetting for this session. * For this setting to make effect, the Backend must support * LaTeX typesetting (as indicated by the capabilities() flag. * @param enable true to enable, false to disable typesetting */ virtual void setTypesettingEnabled(bool enable); /** * Updates the worksheet path in the session. * This can be useful to set the path of the currently opened * Cantor project file in the backend interpreter. * Default implementation does nothing. Derived classes have * to implement the proper logic if this feature is supported. * @param path the new absolute path to the worksheet. */ virtual void setWorksheetPath(const QString& path); /** * Returns the Backend, this Session is for * @return the Backend, this Session is for */ Backend* backend(); /** * Returns the status this Session has * @return the status this Session has */ Cantor::Session::Status status(); /** * Returns whether typesetting is enabled or not * @return whether typesetting is enabled or not */ bool isTypesettingEnabled(); /** * Returns the next available Expression id * It is basically a counter, incremented for * each new Expression * @return next Expression id */ int nextExpressionId(); protected: /** * Change the status of the Session. This will cause the * stausChanged signal to be emitted * @param newStatus the new status of the session */ void changeStatus(Cantor::Session::Status newStatus); /** * Session can process one single expression at one time. * Any other expressions submitted by the user are queued first until they get processed. * The expression queue implements the FIFO mechanism. * The queud expression have the status \c Expression::Queued. */ QList& expressionQueue() const; /** * Execute first expression in expression queue. * Also, this function changes the status from Queued to Computing. * @see expressionQueue() const */ 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 updateVariables(); + + /** + * Setting variable model, usefull, if model constructor requires functional session + */ + void setVariableModel(DefaultVariableModel* model); + Q_SIGNALS: void statusChanged(Cantor::Session::Status newStatus); void loginStarted(); void loginDone(); void error(const QString& msg); private: SessionPrivate* d; }; } #endif /* _SESSION_H */ diff --git a/src/panelplugins/variablemgr/variablemanagerwidget.cpp b/src/panelplugins/variablemgr/variablemanagerwidget.cpp index 0d31fec1..e7e38c39 100644 --- a/src/panelplugins/variablemgr/variablemanagerwidget.cpp +++ b/src/panelplugins/variablemgr/variablemanagerwidget.cpp @@ -1,186 +1,186 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2010 Alexander Rieder */ #include "variablemanagerwidget.h" #include #include #include #include #include #include #include #include "session.h" #include "extension.h" #include "backend.h" #include "ui_newvardlg.h" VariableManagerWidget::VariableManagerWidget(Cantor::Session* session, QWidget* parent) : QWidget(parent), m_session(nullptr), m_model(nullptr), m_table(new QTreeView(this)) { QVBoxLayout* layout=new QVBoxLayout(this); layout->addWidget(m_table, 1); m_table->setRootIsDecorated(false); QHBoxLayout* btnLayout=new QHBoxLayout(); int size=KIconLoader::global()->currentSize(KIconLoader::MainToolbar); QToolButton* m_newBtn=new QToolButton(this); m_newBtn->setIcon(QIcon::fromTheme(QLatin1String("document-new"))); m_newBtn->setToolTip(i18n("Add new variable")); m_newBtn->setIconSize(QSize(size, size)); connect(m_newBtn, &QToolButton::clicked, this, &VariableManagerWidget::newVariable); btnLayout->addWidget(m_newBtn); QToolButton* m_loadBtn=new QToolButton(this); m_loadBtn->setIcon(QIcon::fromTheme(QLatin1String("document-open"))); m_loadBtn->setToolTip(i18n("Load Variables")); m_loadBtn->setIconSize(QSize(size, size)); connect(m_loadBtn, &QToolButton::clicked, this, &VariableManagerWidget::load); btnLayout->addWidget(m_loadBtn); QToolButton* m_saveBtn=new QToolButton(this); m_saveBtn->setIcon(QIcon::fromTheme(QLatin1String("document-save"))); m_saveBtn->setToolTip(i18n("Store Variables")); m_saveBtn->setIconSize(QSize(size, size)); connect(m_saveBtn, &QToolButton::clicked, this, &VariableManagerWidget::save); btnLayout->addWidget(m_saveBtn); QToolButton* m_clearBtn=new QToolButton(this); m_clearBtn->setIcon(QIcon::fromTheme(QLatin1String("edit-clear"))); m_clearBtn->setToolTip(i18n("Clear Variables")); m_clearBtn->setIconSize(QSize(size, size)); connect(m_clearBtn, &QToolButton::clicked, this, &VariableManagerWidget::clearVariables); btnLayout->addWidget(m_clearBtn); layout->addLayout(btnLayout); setSession(session); //check for the methods the backend actually supports, and disable the buttons accordingly Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); if(ext->loadVariables(QString()).isNull()) m_loadBtn->setDisabled(true); if(ext->saveVariables(QString()).isNull()) m_saveBtn->setDisabled(true); if(ext->addVariable(QString(), QString()).isNull()) m_newBtn->setDisabled(true); if(ext->clearVariables().isNull()) m_clearBtn->setDisabled(true); } void VariableManagerWidget::setSession(Cantor::Session* session) { m_session=session; if(session) { - m_model=session->variableModel(); + m_model=session->variableDataModel(); if(m_table) m_table->setModel(m_model); } } void VariableManagerWidget::clearVariables() { int btn=KMessageBox::questionYesNo(this, i18n("Are you sure you want to remove all variables?"), i18n("Confirmation - Cantor")); if(btn==KMessageBox::Yes) { m_model->removeRows(0, m_model->rowCount()); //evaluate the "clear" command Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); const QString& cmd=ext->clearVariables(); emit runCommand(cmd); //HACK? should the model detect that this happened on its own? //inform the model that all variables have been removed. //Do so by trying to evaluate the clearVariables slot of //DefaultVariableModel. If our model isn't one of those, //this call will just do nothing. QMetaObject::invokeMethod(m_model, "clearVariables", Qt::QueuedConnection); } } void VariableManagerWidget::save() { const QString file=QFileDialog::getSaveFileName(this, i18n("Save"), QString(), QString()); if (file.trimmed().isEmpty()) return; Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); const QString& cmd=ext->saveVariables(file); emit runCommand(cmd); } void VariableManagerWidget::load() { const QString file=QFileDialog::getOpenFileName(this, i18n("Load file"), QString(), QString()); if (file.trimmed().isEmpty()) return; Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); const QString& cmd=ext->loadVariables(file); emit runCommand(cmd); } void VariableManagerWidget::newVariable() { QPointer dlg=new QDialog(this); QWidget *widget=new QWidget(dlg); Ui::NewVariableDialogBase base; base.setupUi(widget); QVBoxLayout *mainLayout = new QVBoxLayout; dlg->setLayout(mainLayout); base.buttonBox->button(QDialogButtonBox::Ok)->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogOkButton)); base.buttonBox->button(QDialogButtonBox::Cancel)->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogCancelButton)); connect(base.buttonBox, SIGNAL(accepted()), dlg, SLOT(accept()) ); connect(base.buttonBox, SIGNAL(rejected()), dlg, SLOT(reject()) ); mainLayout->addWidget(widget); if( dlg->exec()) { const QString& name=base.name->text(); const QString& val=base.value->text(); Cantor::VariableManagementExtension* ext= dynamic_cast(m_session->backend()->extension(QLatin1String("VariableManagementExtension"))); const QString& cmd=ext->addVariable(name, val); emit runCommand(cmd); } delete dlg; }