diff --git a/src/backends/R/CMakeLists.txt b/src/backends/R/CMakeLists.txt --- a/src/backends/R/CMakeLists.txt +++ b/src/backends/R/CMakeLists.txt @@ -1,7 +1,7 @@ include_directories(${R_INCLUDEDIR}) LINK_DIRECTORIES(${R_SHAREDLIBDIR}) -add_subdirectory(rserver) +#add_subdirectory(rserver) set( RBackend_SRCS rbackend.cpp @@ -13,10 +13,11 @@ rsettingswidget.cpp ) -kconfig_add_kcfg_files(RBackend_SRCS rserver/settings.kcfgc) +kconfig_add_kcfg_files(RBackend_SRCS settings.kcfgc) +install(FILES rbackend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) -set(network_xml rserver/org.kde.Cantor.R.xml) -QT5_ADD_DBUS_INTERFACE(RBackend_SRCS ${network_xml} rserver_interface ) +#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) diff --git a/src/backends/R/rbackend.h b/src/backends/R/rbackend.h --- a/src/backends/R/rbackend.h +++ b/src/backends/R/rbackend.h @@ -34,7 +34,6 @@ QString version() const override; Cantor::Session *createSession(); Cantor::Backend::Capabilities capabilities() const; - bool requirementsFullfilled() const; virtual QWidget* settingsWidget(QWidget* parent) const; virtual KConfigSkeleton* config() const; diff --git a/src/backends/R/rbackend.cpp b/src/backends/R/rbackend.cpp --- a/src/backends/R/rbackend.cpp +++ b/src/backends/R/rbackend.cpp @@ -69,20 +69,14 @@ Cantor::Backend::Completion; } -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(); + return RBackendSettings::self(); } QUrl RBackend::helpUrl() const diff --git a/src/backends/R/rbackend.kcfg b/src/backends/R/rbackend.kcfg new file mode 100644 --- /dev/null +++ b/src/backends/R/rbackend.kcfg @@ -0,0 +1,16 @@ + + + + + + + true + + + + + + diff --git a/src/backends/R/rexpression.h b/src/backends/R/rexpression.h --- a/src/backends/R/rexpression.h +++ b/src/backends/R/rexpression.h @@ -25,21 +25,24 @@ class RExpression : public Cantor::Expression { - Q_OBJECT - public: - enum ServerReturnCode{SuccessCode=0, ErrorCode, InterruptedCode}; + Q_OBJECT + +public: + RExpression( Cantor::Session* session); ~RExpression(); void evaluate(); void interrupt(); void addInformation(const QString& information); - public Q_SLOTS: - void finished(int returnCode, const QString& text); - void evaluationStarted(); + void parseOutput(QString output); + void parseError(QString error); + +public Q_SLOTS: void showFilesAsResult(const QStringList& files); - private: + +private: bool m_isHelpRequest; }; diff --git a/src/backends/R/rexpression.cpp b/src/backends/R/rexpression.cpp --- a/src/backends/R/rexpression.cpp +++ b/src/backends/R/rexpression.cpp @@ -34,6 +34,7 @@ #include #include #include +#include RExpression::RExpression( Cantor::Session* session ) : Cantor::Expression(session) { @@ -51,44 +52,78 @@ { qDebug()<<"evaluating "<(session())->queueExpression(this); + + RSession* session = dynamic_cast(this->session()); + + /* check if the command is a quit command. following are quit commands supported by R + * quit(save = "default", status = 0, runLast = TRUE) + * q(save = "default", status = 0, runLast = TRUE) + */ + QRegExp regexp; + regexp.setCaseSensitivity(Qt::CaseSensitive); + regexp.setPattern(QLatin1String("\\s*q\\s*\\([\\w\\W]*\\)\\s*$|\\s*quit\\s*\\([\\w\\W]*\\)\\s*$")); + if(regexp.exactMatch(command().trimmed())) { + // since it's a quit command, logout from the session + setStatus(Cantor::Expression::Done); + session->logout(); + return; + } + + // done evaluation, finally running the command + session->runExpression(this); + + } -void RExpression::interrupt() +void RExpression::parseOutput(QString output) { - qDebug()<<"interrupting command"; - if(status()==Cantor::Expression::Computing) - session()->interrupt(); - setStatus(Cantor::Expression::Interrupted); + // remove garabage from output + + output.replace(command(), QLatin1String("")); + output.replace(QLatin1String(">"), QLatin1String("")); + output = output.trimmed(); + + qDebug() << " final output of the command " << command() << output << endl; + + if(m_isHelpRequest) + setResult(new Cantor::HelpResult(output)); + else + setResult(new Cantor::TextResult(output)); + + setStatus(Cantor::Expression::Done); } -void RExpression::finished(int returnCode, const QString& text) +void RExpression::parseError(QString error) { - if(returnCode==RExpression::SuccessCode) - { - setResult(new Cantor::TextResult(Qt::convertFromPlainText(text))); - setStatus(Cantor::Expression::Done); - }else if (returnCode==RExpression::ErrorCode) - { - setResult(new Cantor::TextResult(Qt::convertFromPlainText(text))); - setStatus(Cantor::Expression::Error); - setErrorMessage(Qt::convertFromPlainText(text)); - } + error.replace(command(), QLatin1String("")); + error.replace(QLatin1String(">"), QLatin1String("")); + error = error.trimmed(); + + setResult(new Cantor::TextResult(error)); + + setStatus(Cantor::Expression::Error); + setErrorMessage(error); } -void RExpression::evaluationStarted() +void RExpression::interrupt() { - setStatus(Cantor::Expression::Computing); + qDebug()<<"interrupting command"; + if(status()==Cantor::Expression::Computing) + session()->interrupt(); + setStatus(Cantor::Expression::Interrupted); } void RExpression::addInformation(const QString& information) { - static_cast(session())->sendInputToServer(information); + /* not using this anymore since it was making use of rserver + * + */ +// static_cast(session())->sendInputToServer(information); } void RExpression::showFilesAsResult(const QStringList& files) diff --git a/src/backends/R/rsession.h b/src/backends/R/rsession.h --- a/src/backends/R/rsession.h +++ b/src/backends/R/rsession.h @@ -25,15 +25,15 @@ #include #include "session.h" -#include "rserver_interface.h" + class RExpression; -class KProcess; +class QProcess; class RSession : public Cantor::Session { - Q_OBJECT - public: + Q_OBJECT +public: RSession( Cantor::Backend* backend); ~RSession(); @@ -46,24 +46,27 @@ Cantor::CompletionObject* completionFor(const QString& command, int index=-1); QSyntaxHighlighter* syntaxHighlighter(QObject* parent); - void queueExpression(RExpression* expr); - void sendInputToServer(const QString& input); + void runExpression(RExpression* expr); - protected Q_SLOTS: - void serverChangedStatus(int status); - void runNextExpression(); +protected Q_SLOTS: void receiveSymbols(const QStringList& v, const QStringList & f); void fillSyntaxRegExps(QVector& v, QVector& f); - Q_SIGNALS: +Q_SIGNALS: void symbolsChanged(); - private: - KProcess* m_rProcess; - org::kde::Cantor::R* m_rServer; - QList m_expressionQueue; +public Q_SLOTS: + void readOutput(); + void readError(); + void processStarted(); + void currentExpressionStatusChanged(Cantor::Expression::Status status); +private: + QProcess* m_Process; + RExpression* m_CurrentExpression; + QString m_Output; + QString m_Error; /* Available variables and functions, TODO make full classes and type info */ QStringList m_variables; QStringList m_functions; diff --git a/src/backends/R/rsession.cpp b/src/backends/R/rsession.cpp --- a/src/backends/R/rsession.cpp +++ b/src/backends/R/rsession.cpp @@ -25,79 +25,169 @@ #include #include -#include +#include +#include + #include -RSession::RSession( Cantor::Backend* backend) : Session(backend) +RSession::RSession( Cantor::Backend* backend) : Session(backend), + m_Process(0), + m_CurrentExpression(0) { - qDebug(); - m_rProcess=0; } RSession::~RSession() { qDebug(); - m_rProcess->terminate(); + m_Process->terminate(); } void RSession::login() { qDebug()<<"login"; - if(m_rProcess) - m_rProcess->deleteLater(); - m_rProcess=new KProcess(this); - m_rProcess->setOutputChannelMode(KProcess::ForwardedChannels); - (*m_rProcess)<start(); + m_Process=new QProcess(this); + m_Process->setProcessChannelMode(QProcess::SeparateChannels); - m_rServer=new org::kde::Cantor::R(QString::fromLatin1("org.kde.cantor_rserver-%1").arg(m_rProcess->pid()), QLatin1String("/R"), QDBusConnection::sessionBus(), this); + QString path = QStandardPaths::findExecutable(QLatin1String("R")); + qDebug() << path; - connect(m_rServer, SIGNAL(statusChanged(int)), this, SLOT(serverChangedStatus(int))); - connect(m_rServer,SIGNAL(symbolList(const QStringList&,const QStringList&)),this,SLOT(receiveSymbols(const QStringList&,const QStringList&))); + if(QStandardPaths::findExecutable(QLatin1String("R")).isEmpty()) + qDebug() << "Could not find the R exe" << endl; - changeStatus(Cantor::Session::Done); - connect(m_rServer, SIGNAL(ready()), this, SIGNAL(ready())); + + m_Process->setProgram(QStandardPaths::findExecutable(QLatin1String("R"))); + QStringList args; + /* + * interactive - forcing an interactive session + * quiet - don't print the startup message + * no-save - don't save the workspace after the end of session(when users quits). Should the user be allowed to save the session? + */ + args.append(QLatin1String("--interactive")); + args.append(QLatin1String("--quiet")); + args.append(QLatin1String("--no-save")); + m_Process->setArguments(args); + + 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(); + +} + + +void RSession::readOutput() +{ + while(m_Process->bytesAvailable()) { + m_Output.append(QString::fromLocal8Bit(m_Process->readLine())); + } + + if(m_CurrentExpression && !m_Output.isEmpty() && m_Output.trimmed().endsWith(QLatin1String(">"))) { + m_CurrentExpression->parseOutput(m_Output); + m_Output.clear(); + } +} + +void RSession::readError() +{ + m_Error = QString::fromLocal8Bit(m_Process->readAllStandardError()); + m_CurrentExpression->parseError(m_Error); + m_Error.clear(); +} + +void RSession::processStarted() +{ + qDebug() << m_Process->program() << " with pid " << m_Process->processId() << " started successfully " << endl; + emit ready(); } void RSession::logout() { - qDebug()<<"logout"; - m_rProcess->terminate(); + qDebug()<<"logout" << endl; + + // this happens if the user gives quit command + if(status() == Cantor::Session::Running) + changeStatus(Cantor::Session::Done); + + if(m_Process && m_Process->state() == QProcess::Running){ + qDebug () << " Killing the process "; + m_Process->kill(); + } } void RSession::interrupt() { - qDebug()<<"interrupt" << m_rProcess->pid(); - if (m_rProcess->pid()) - kill(m_rProcess->pid(), 2); - m_expressionQueue.removeFirst(); + qDebug()<<"interrupt" << m_Process->pid(); changeStatus(Cantor::Session::Done); } Cantor::Expression* RSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) { qDebug()<<"evaluating: "<setFinishingBehavior(behave); expr->setCommand(cmd); - expr->evaluate(); + /* start evaluating only if the process is in running state + * imo this is a late check, can this be done somewhere before ? + */ + if(m_Process && m_Process->state() == QProcess::NotRunning){ + changeStatus(Cantor::Session::Done); + return expr; + } - changeStatus(Cantor::Session::Running); + expr->evaluate(); return expr; } +void RSession::runExpression(RExpression* expr) +{ + m_CurrentExpression = expr; + connect(m_CurrentExpression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionStatusChanged(Cantor::Expression::Status))); + + QString currentCmd = m_CurrentExpression->command(); + currentCmd.trimmed(); + currentCmd += QLatin1String("\n"); + + m_Process->write(currentCmd.toLocal8Bit()); + + +} + +void RSession::currentExpressionStatusChanged(Cantor::Expression::Status status) +{ + switch (status) { + + case Cantor::Expression::Computing: + break; + case Cantor::Expression::Interrupted: + changeStatus(Cantor::Session::Done); + break; + case Cantor::Expression::Done: + case Cantor::Expression::Error: + changeStatus(Cantor::Session::Done); + + } +} Cantor::CompletionObject* RSession::completionFor(const QString& command, int index) { - RCompletionObject *cmp=new RCompletionObject(command, index, this); - connect(m_rServer,SIGNAL(completionFinished(const QString&,const QStringList&)),cmp,SLOT(receiveCompletions(const QString&,const QStringList&))); - connect(cmp,SIGNAL(requestCompletion(const QString&)),m_rServer,SLOT(completeCommand(const QString&))); - return cmp; + /* + * since we won't be using rserver anymore, we will have to find a different method to make completion work + * can we use R's command line interface for this ? + */ + +// RCompletionObject *cmp=new RCompletionObject(command, index, this); +// connect(m_rServer,SIGNAL(completionFinished(const QString&,const QStringList&)),cmp,SLOT(receiveCompletions(const QString&,const QStringList&))); +// connect(cmp,SIGNAL(requestCompletion(const QString&)),m_rServer,SLOT(completeCommand(const QString&))); +// return cmp; + return 0; } QSyntaxHighlighter* RSession::syntaxHighlighter(QObject* parent) @@ -130,60 +220,5 @@ emit symbolsChanged(); } -void RSession::queueExpression(RExpression* expr) -{ - m_expressionQueue.append(expr); - - if(status()==Cantor::Session::Done) - QTimer::singleShot(0, this, SLOT(runNextExpression())); -} - - -void RSession::serverChangedStatus(int status) -{ - qDebug()<<"changed status to "<command(); - } - - if(m_expressionQueue.isEmpty()) - changeStatus(Cantor::Session::Done); - else - runNextExpression(); - } - else - changeStatus(Cantor::Session::Running); -} - -void RSession::runNextExpression() -{ - if (m_expressionQueue.isEmpty()) - return; - disconnect(m_rServer, SIGNAL(expressionFinished(int, const QString&)), 0, 0); - disconnect(m_rServer, SIGNAL(inputRequested(const QString&)), 0, 0); - disconnect(m_rServer, SIGNAL(showFilesNeeded(const QStringList&)), 0, 0); - qDebug()<<"size: "<command(); - - connect(m_rServer, SIGNAL(expressionFinished(int, const QString &)), expr, SLOT(finished(int, const QString&))); - connect(m_rServer, SIGNAL(inputRequested(const QString&)), expr, SIGNAL(needsAdditionalInformation(const QString&))); - connect(m_rServer, SIGNAL(showFilesNeeded(const QStringList&)), expr, SLOT(showFilesAsResult(const QStringList&))); - - 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); -} #include "rsession.moc" diff --git a/src/backends/R/settings.kcfgc b/src/backends/R/settings.kcfgc new file mode 100644 --- /dev/null +++ b/src/backends/R/settings.kcfgc @@ -0,0 +1,3 @@ +File=rbackend.kcfg +ClassName=RBackendSettings +Singleton=true