diff --git a/src/backends/R/CMakeLists.txt b/src/backends/R/CMakeLists.txt index ff77af16..d4485615 100644 --- a/src/backends/R/CMakeLists.txt +++ b/src/backends/R/CMakeLists.txt @@ -1,29 +1,30 @@ 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) install( FILES cantor_r.knsrc DESTINATION ${KDE_INSTALL_CONFDIR} ) diff --git a/src/backends/R/rhighlighter.cpp b/src/backends/R/rhighlighter.cpp index 9ae67936..076baacf 100644 --- a/src/backends/R/rhighlighter.cpp +++ b/src/backends/R/rhighlighter.cpp @@ -1,92 +1,124 @@ /* 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 #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) { 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() +void RHighlighter::addUserStuff(const QStringList& names, QVector& vector) { + for (const QString s : names) + if (!s.contains(QRegExp(QLatin1String("[^A-Za-z0-9_.]")))) + vector.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); + emit rulesChanged(); } + +void RHighlighter::removeUserStuff(const QStringList& names, QVector& vector) +{ + for (const QString var : names) + for (int i = 0; i < vector.size(); i++) + if (vector[i].pattern() == QLatin1String("\\b")+var+QLatin1String("\\b")) + { + vector.remove(i); + break; + } + + emit rulesChanged(); +} + +void RHighlighter::addUserVariable(const QStringList& vars) +{ + addUserStuff(vars, variables); +} + +void RHighlighter::removeUserVariable(const QStringList& vars) +{ + removeUserStuff(vars, variables); +} + +void RHighlighter::removeUserFunction(const QStringList& funcs) +{ + removeUserStuff(funcs, functions); +} + +void RHighlighter::addUserFunction(const QStringList& funcs) +{ + addUserStuff(funcs, functions); +} diff --git a/src/backends/R/rhighlighter.h b/src/backends/R/rhighlighter.h index a08a6548..527af824 100644 --- a/src/backends/R/rhighlighter.h +++ b/src/backends/R/rhighlighter.h @@ -1,56 +1,58 @@ /* 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 RHighlighter : public Cantor::DefaultHighlighter { Q_OBJECT public: explicit RHighlighter( QObject* parent); ~RHighlighter() override = default; protected: void highlightBlock(const QString &text) override; public Q_SLOTS: - void refreshSyntaxRegExps(); - void updateHighlighting(); - - Q_SIGNALS: - void syntaxRegExps(QVector&,QVector&); + void addUserVariable(const QStringList& vars); + void removeUserVariable(const QStringList& vars); + void addUserFunction(const QStringList& funcs); + void removeUserFunction(const QStringList& funcs); private: inline void formatRule(const QRegExp &p, const QTextCharFormat &fmt, const QString& text,bool shift=false); inline void massFormat(const QVector& rules, const QTextCharFormat &fmt, const QString& text,bool shift=false); + void addUserStuff(const QStringList& names, QVector& vector); + void removeUserStuff(const QStringList& names, QVector& vector); + 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 6ebb36bb..b99f0714 100644 --- a/src/backends/R/rserver/rserver.cpp +++ b/src/backends/R/rserver/rserver.cpp @@ -1,521 +1,514 @@ /* 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 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), +m_variableModel(new RVariableModel(this)), +m_needUpdate(false) { } 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(); + m_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 } expressionQueue().first()->interrupt(); expressionQueue().removeFirst(); foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Done); 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())); + connect(m_variableModel, &Cantor::DefaultVariableModel::variablesAdded, h, &RHighlighter::addUserVariable); + connect(m_variableModel, &Cantor::DefaultVariableModel::variablesRemoved, h, &RHighlighter::removeUserVariable); + connect(m_variableModel, &RVariableModel::functionsAdded, h, &RHighlighter::addUserFunction); + connect(m_variableModel, &RVariableModel::functionsRemoved, h, &RHighlighter::removeUserFunction); return h; } -void RSession::fillSyntaxRegExps(QVector& v, QVector& f) -{ - // WARNING: current implementation as-in-maxima is a performance hit - // think about grouping expressions together or only fetching needed ones - v.clear(); f.clear(); - - foreach (const QString s, m_variables) - if (!s.contains(QRegExp(QLatin1String("[^A-Za-z0-9_.]")))) - v.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); - foreach (const QString s, m_functions) - if (!s.contains(QRegExp(QLatin1String("[^A-Za-z0-9_.]")))) - f.append(QRegExp(QLatin1String("\\b")+s+QLatin1String("\\b"))); - - emit syntaxRegExpsFilled(); -} - -void RSession::receiveSymbols(const QStringList& vars, const QStringList& values, const QStringList & funcs) -{ - m_variables = vars; - for (int i = 0; i < vars.count(); i++) - { - m_variableModel->addVariable(vars[i], values[i]); - } - m_functions = funcs; - - emit symbolsChanged(); -} - void RSession::serverChangedStatus(int status) { qDebug()<<"changed status to "<(expressionQueue().takeFirst()); + m_needUpdate |= !expr->isInternal(); qDebug()<<"done running "<command(); } if(expressionQueue().isEmpty()) - changeStatus(Cantor::Session::Done); + if (m_needUpdate) + { + m_needUpdate = false; + m_variableModel->update(); + } + else + changeStatus(Cantor::Session::Done); else runFirstExpression(); } 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() { return m_variableModel; } + +void RSession::updateSymbols(const RVariableModel* model) +{ + 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..c186b7fe 100644 --- a/src/backends/R/rsession.h +++ b/src/backends/R/rsession.h @@ -1,75 +1,72 @@ /* 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 QProcess; +class RVariableModel; 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; + RVariableModel* m_variableModel; + bool m_needUpdate; }; #endif /* _RSESSION_H */ diff --git a/src/backends/R/rvariablemodel.cpp b/src/backends/R/rvariablemodel.cpp new file mode 100644 index 00000000..bfde75de --- /dev/null +++ b/src/backends/R/rvariablemodel.cpp @@ -0,0 +1,75 @@ +/* + 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; + for (int i = 0; i < names.size(); i++) + vars.append(Variable{names[i], values[i]}); + setVariables(vars); + + // Handle functions + QStringList addedFuncs; + QStringList removedFuncs; + + int i = 0; + while (i < m_functions.size()) + if (!funcs.contains(m_functions[i])) + { + removedFuncs << m_functions[i]; + m_functions.removeAt(i); + } + else + i++; + + for (const QString newFunc : funcs) + if(!m_functions.contains(newFunc)) + { + addedFuncs << newFunc; + m_functions << newFunc; + } + + if (!addedFuncs.isEmpty()) + emit functionsAdded(addedFuncs); + + if (!removedFuncs.isEmpty()) + emit functionsRemoved(removedFuncs); +} + +void RVariableModel::clearFunctions() +{ + emit functionsRemoved(m_functions); + m_functions.clear(); +} diff --git a/src/backends/R/rvariablemodel.h b/src/backends/R/rvariablemodel.h new file mode 100644 index 00000000..bcd53bd3 --- /dev/null +++ b/src/backends/R/rvariablemodel.h @@ -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 +*/ + +#ifndef _RVARIABLEMODEL_H +#define _RVARIABLEMODEL_H + +#include "defaultvariablemodel.h" + +class RSession; + +class RVariableModel : public Cantor::DefaultVariableModel +{ + Q_OBJECT + public: + RVariableModel( RSession* session); + ~RVariableModel() override = default; + + void update() override; + void clearFunctions(); + + Q_SIGNALS: + void functionsAdded(const QStringList& names); + void functionsRemoved(const QStringList& names); + + public Q_SLOTS: + void parseResult(const QStringList& names, const QStringList& values, const QStringList& funcs); + + private: + QStringList m_functions; +}; + +#endif /* _RVARIABLEMODEL_H */