diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp index 259319fe..b45490d9 100644 --- a/src/backends/julia/juliasession.cpp +++ b/src/backends/julia/juliasession.cpp @@ -1,357 +1,362 @@ /* 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 "juliaextensions.h" #include "juliabackend.h" #include "juliacompletionobject.h" #include +const QRegularExpression JuliaSession::typeVariableInfo = QRegularExpression(QLatin1String("\\w+\\[")); + JuliaSession::JuliaSession(Cantor::Backend *backend) : Session(backend) , m_process(nullptr) , m_interface(nullptr) , m_currentExpression(nullptr) , m_variableModel(new Cantor::DefaultVariableModel(this)) { } 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(); // Plots integration if (integratePlots()) { runJuliaCommand( QLatin1String("import GR; ENV[\"GKS_WSTYPE\"] = \"nul\"") ); } emit loginDone(); qDebug() << "login to julia " << JULIA_VERSION_STRING << "done"; } void JuliaSession::logout() { m_process->terminate(); JuliaKeywords::instance()->clearVariables(); JuliaKeywords::instance()->clearFunctions(); } 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) { JuliaExpression *expr = new JuliaExpression(this); 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; } 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_runningExpressions.removeAll(m_currentExpression); listVariables(); changeStatus(Cantor::Session::Done); } void JuliaSession::runExpression(JuliaExpression *expr) { m_runningExpressions.append(expr); m_currentExpression = expr; runJuliaCommandAsync(expr->command()); } 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() { QStringList ignoredVariables; ignoredVariables // These are tech variables of juliaserver << QLatin1String("__originalSTDOUT__") << QLatin1String("__originalSTDERR__"); // Wrapping removed marker to quotes auto rem_marker = QString::fromLatin1("\"%1\"") .arg(JuliaVariableManagementExtension::REMOVED_VARIABLE_MARKER); // Clear current symbols JuliaKeywords::instance()->clearVariables(); JuliaKeywords::instance()->clearFunctions(); QStringList processed_modules; // modules we have processed QStringList modules_to_process; // modules in queue modules_to_process << QLatin1String("__GLOBAL__"); // starting from global while (modules_to_process.size() > 0) { // Get from queue auto module = modules_to_process.front(); modules_to_process.pop_front(); if (processed_modules.contains(module)) { continue; } processed_modules << module; // Get whos() output, maybe from cache QString whos_output; if (module == QLatin1String("__GLOBAL__")) { runJuliaCommand(QLatin1String("whos()")); whos_output = getOutput(); } else { auto it = m_whos_cache.find(module); if (it == m_whos_cache.end()) { runJuliaCommand(QString::fromLatin1("whos(%1)").arg(module)); whos_output = getOutput(); m_whos_cache[module] = whos_output; } else { whos_output = it.value(); } } // In this lists we will collect symbols to apply `show` to them // in one DBus call QStringList batchCommands; QStringList batchTypes; QStringList batchNames; for (auto line : whos_output.split(QLatin1String("\n"))) { QString name = line.simplified().split(QLatin1String(" ")).first().simplified(); if (name.isEmpty()) { // some empty line continue; } QString type = line.simplified().split(QLatin1String(" ")).last().simplified(); #if JULIA_VERSION_MINOR > 5 if (line.contains(QLatin1String("#"))) type = QLatin1String("Function"); #endif if (ignoredVariables.contains(name)) { // Ignored variable continue; } if (type == QLatin1String("Module")) { // Found module, place in queue modules_to_process.append(name); continue; } if (type == QLatin1String("Function")) { // Found function JuliaKeywords::instance()->addFunction(name); continue; } if (module != QLatin1String("__GLOBAL__")) { continue; // Don't add variables not included on global scope } // Add to batch batchCommands << QString::fromLatin1("show(%1);").arg(name); batchTypes << type; batchNames << name; } if (batchCommands.isEmpty()) { continue; // nothing to do } // Run batched command runJuliaCommand( batchCommands.join(QLatin1String("print(\"__CANTOR_DELIM__\");")) ); auto values = getOutput().split(QLatin1String("__CANTOR_DELIM__")); for (int i = 0; i < values.size(); i++) { auto value = values[i].simplified(); auto type = batchTypes[i]; auto name = batchNames[i]; if (type == QLatin1String("ASCIIString")) { if (value == rem_marker) { // This is removed variable m_variableModel->removeVariable(name); continue; } } // Register variable - m_variableModel->addVariable(name, value); + // 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); } } emit updateHighlighter(); } QAbstractItemModel *JuliaSession::variableModel() { return m_variableModel; } bool JuliaSession::integratePlots() { return JuliaSettings::integratePlots(); } diff --git a/src/backends/julia/juliasession.h b/src/backends/julia/juliasession.h index ee40e56c..71363837 100644 --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -1,172 +1,174 @@ /* 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 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 */ 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) 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; /** * @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; /// 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 occured during the last command execution */ bool getWasException(); /** * Updates variable model by querying all modules in scope with whos command */ void listVariables(); };