diff --git a/src/backends/julia/CMakeLists.txt b/src/backends/julia/CMakeLists.txt --- a/src/backends/julia/CMakeLists.txt +++ b/src/backends/julia/CMakeLists.txt @@ -7,6 +7,7 @@ juliaexpression.cpp juliakeywords.cpp juliahighlighter.cpp + juliaextensions.cpp ) kconfig_add_kcfg_files(JuliaBackend_SRCS settings.kcfgc) diff --git a/src/backends/julia/julia.qrc b/src/backends/julia/julia.qrc --- a/src/backends/julia/julia.qrc +++ b/src/backends/julia/julia.qrc @@ -3,4 +3,9 @@ keywords.xml + + variables_cleaner.jl + variables_loader.jl + variables_saver.jl + diff --git a/src/backends/julia/juliabackend.cpp b/src/backends/julia/juliabackend.cpp --- a/src/backends/julia/juliabackend.cpp +++ b/src/backends/julia/juliabackend.cpp @@ -24,11 +24,14 @@ #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); } QString JuliaBackend::id() const @@ -43,7 +46,8 @@ Cantor::Backend::Capabilities JuliaBackend::capabilities() const { - return Cantor::Backend::SyntaxHighlighting; + return Cantor::Backend::SyntaxHighlighting | + Cantor::Backend::VariableManagement; } QString JuliaBackend::description() const diff --git a/src/backends/julia/juliaextensions.h b/src/backends/julia/juliaextensions.h new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliaextensions.h @@ -0,0 +1,47 @@ +/* + 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 + +#define JULIA_EXT_CDTOR_DECL(name) Julia##name##Extension(QObject* parent); \ + ~Julia##name##Extension(); + + +class JuliaVariableManagementExtension: public Cantor::VariableManagementExtension +{ +public: + JULIA_EXT_CDTOR_DECL(VariableManagement) + + static const QString REMOVED_VARIABLE_MARKER; + + virtual QString addVariable( + const QString &name, + const QString &value) override; + + virtual QString setValue( + const QString &name, + const QString &value) override; + + virtual QString removeVariable(const QString &name) override; + virtual QString saveVariables(const QString &fileName) override; + virtual QString loadVariables(const QString &fileName) override; + virtual QString clearVariables() override; +}; diff --git a/src/backends/julia/juliaextensions.cpp b/src/backends/julia/juliaextensions.cpp new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliaextensions.cpp @@ -0,0 +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) 2016 Ivan Lakhtanov + */ +#include "juliaextensions.h" + +#include +#include + +#include "juliautils.h" + +#define JULIA_EXT_CDTOR(name) Julia##name##Extension::Julia##name##Extension(QObject* parent) : name##Extension(parent) {} \ + Julia##name##Extension::~Julia##name##Extension() {} + + +JULIA_EXT_CDTOR(VariableManagement) + +const QString JuliaVariableManagementExtension::REMOVED_VARIABLE_MARKER = + QLatin1String("__REM__"); + +QString JuliaVariableManagementExtension::addVariable( + const QString &name, + const QString &value) +{ + return setValue(name, value); +} + +QString JuliaVariableManagementExtension::setValue( + const QString &name, + const QString &value) +{ + return QString::fromLatin1("%1 = %2").arg(name).arg(value); +} + +QString JuliaVariableManagementExtension::removeVariable(const QString &name) +{ + // There is no way to completely delete object from scope: + // http://docs.julialang.org/en/release-0.4/manual/faq/#how-do-i-delete-an-object-in-memory + return QString::fromLatin1("%1 = \"%2\"") + .arg(name).arg(REMOVED_VARIABLE_MARKER); +} + +QString JuliaVariableManagementExtension::clearVariables() +{ + return fromSource(QLatin1String(":jl/variables_cleaner.jl")) + .arg(REMOVED_VARIABLE_MARKER); +} + +QString JuliaVariableManagementExtension::saveVariables(const QString &fileName) +{ + return fromSource(QLatin1String(":jl/variables_saver.jl")).arg(fileName); +} + +QString JuliaVariableManagementExtension::loadVariables(const QString &fileName) +{ + return fromSource(QLatin1String(":jl/variables_loader.jl")).arg(fileName); +} diff --git a/src/backends/julia/juliahighlighter.cpp b/src/backends/julia/juliahighlighter.cpp --- a/src/backends/julia/juliahighlighter.cpp +++ b/src/backends/julia/juliahighlighter.cpp @@ -27,11 +27,9 @@ JuliaHighlighter::JuliaHighlighter(QObject *parent) : Cantor::DefaultHighlighter(parent) { - addRule(QRegExp(QLatin1String("\\b\\w+(?=\\()")), functionFormat()); - - // Code highlighting the different keywords addKeywords(JuliaKeywords::instance()->keywords()); addVariables(JuliaKeywords::instance()->variables()); + addFunctions(JuliaKeywords::instance()->functions()); } void JuliaHighlighter::highlightBlock(const QString &text) @@ -145,7 +143,16 @@ void JuliaHighlighter::updateHighlight() { + for (const auto &var : JuliaKeywords::instance()->removedVariables()) { + removeRule(var); + } + + for (const auto &func : JuliaKeywords::instance()->removedFunctions()) { + removeRule(func); + } + addVariables(JuliaKeywords::instance()->variables()); + addFunctions(JuliaKeywords::instance()->functions()); rehighlight(); } diff --git a/src/backends/julia/juliakeywords.h b/src/backends/julia/juliakeywords.h --- a/src/backends/julia/juliakeywords.h +++ b/src/backends/julia/juliakeywords.h @@ -24,14 +24,26 @@ class JuliaKeywords { public: - static JuliaKeywords* instance(); + static JuliaKeywords *instance(); const QStringList &keywords() const { return m_keywords; } + const QStringList &variables() const { return m_variables; } + const QStringList &removedVariables() const { return m_removedVariables; } + void clearVariables(); + void addVariable(const QString &variable); + + const QStringList &functions() const { return m_functions; } + const QStringList &removedFunctions() const { return m_removedFunctions; } + void clearFunctions(); + void addFunction(const QString &function); private: QStringList m_keywords; QStringList m_variables; + QStringList m_removedVariables; + QStringList m_functions; + QStringList m_removedFunctions; JuliaKeywords() {} ~JuliaKeywords() {} diff --git a/src/backends/julia/juliakeywords.cpp b/src/backends/julia/juliakeywords.cpp --- a/src/backends/julia/juliakeywords.cpp +++ b/src/backends/julia/juliakeywords.cpp @@ -45,7 +45,7 @@ if (!file.open(QIODevice::ReadOnly)) { qWarning() << "error opening keywords.xml file. highlighting and" - << " completion won't work"; + << "completion won't work"; return; } @@ -56,7 +56,8 @@ const QStringRef name = xml.name(); if (name == QLatin1String("keywords") - || name == QLatin1String("variables")) { + or name == QLatin1String("variables") + or name == QLatin1String("functions")) { while (xml.readNextStartElement()) { Q_ASSERT( xml.isStartElement() && xml.name() == QLatin1String("word") @@ -68,6 +69,8 @@ m_keywords << text; } else if (name == QLatin1String("variables")) { m_variables << text; + } else if (name == QLatin1String("functions")) { + m_functions << text; } } } else { @@ -76,6 +79,32 @@ } if (xml.hasError()) { - qWarning() << "Error parsing keywords.xml: " << xml.errorString(); + qWarning() << "Error parsing keywords.xml:" << xml.errorString(); } } + +void JuliaKeywords::addVariable(const QString &variable) +{ + if (not m_variables.contains(variable)) { + m_variables << variable; + } +} + +void JuliaKeywords::clearVariables() +{ + m_removedVariables = m_variables; + m_variables.clear(); +} + +void JuliaKeywords::addFunction(const QString &function) +{ + if (not m_functions.contains(function)) { + m_functions << function; + } +} + +void JuliaKeywords::clearFunctions() +{ + m_removedFunctions == m_functions; + m_functions.clear(); +} diff --git a/src/backends/julia/juliaserver/juliaserver.cpp b/src/backends/julia/juliaserver/juliaserver.cpp --- a/src/backends/julia/juliaserver/juliaserver.cpp +++ b/src/backends/julia/juliaserver/juliaserver.cpp @@ -50,8 +50,8 @@ qFatal("Unable to create temprorary files for stdout/stderr"); return; } - jl_eval_string("const originalSTDOUT = STDOUT"); - jl_eval_string("const originalSTDERR = STDERR"); + jl_eval_string("const __originalSTDOUT__ = STDOUT"); + jl_eval_string("const __originalSTDERR__ = STDERR"); jl_eval_string( QString::fromLatin1("redirect_stdout(open(\"%1\", \"w\"))") .arg(output.fileName()).toLatin1().constData() @@ -90,8 +90,18 @@ } jl_eval_string("flush(STDOUT)"); jl_eval_string("flush(STDERR)"); - jl_eval_string("redirect_stdout(originalSTDOUT)"); - jl_eval_string("redirect_stderr(originalSTDERR)"); + jl_eval_string("redirect_stdout(__originalSTDOUT__)"); + jl_eval_string("redirect_stderr(__originalSTDERR__)"); + + 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()); diff --git a/src/backends/julia/juliasession.h b/src/backends/julia/juliasession.h --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -19,11 +19,16 @@ */ #pragma once +#include + #include "session.h" class JuliaExpression; class KProcess; class QDBusInterface; +namespace Cantor { + class DefaultVariableModel; +} class JuliaSession: public Cantor::Session { @@ -46,6 +51,10 @@ int index = -1) override; virtual QSyntaxHighlighter *syntaxHighlighter(QObject *parent); + virtual QAbstractItemModel *variableModel() override; + +Q_SIGNALS: + void updateHighlighter(); private Q_SLOTS: void onResultReady(); @@ -57,6 +66,10 @@ QList m_runningExpressions; JuliaExpression *m_currentExpression; + Cantor::DefaultVariableModel *m_variableModel; + + QMap m_whos_cache; + friend JuliaExpression; void runExpression(JuliaExpression *expression); @@ -67,4 +80,6 @@ QString getStringFromServer(const QString &method); QString getOutput(); QString getError(); + + void listVariables(); }; diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp --- a/src/backends/julia/juliasession.cpp +++ b/src/backends/julia/juliasession.cpp @@ -23,17 +23,22 @@ #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" JuliaSession::JuliaSession(Cantor::Backend *backend) : Session(backend) , m_process(nullptr) , m_currentExpression(nullptr) + , m_variableModel(new Cantor::DefaultVariableModel(this)) { } @@ -89,12 +94,15 @@ JuliaSettings::self()->replPath().path() ); + listVariables(); + emit ready(); } void JuliaSession::logout() { m_process->terminate(); + JuliaKeywords::instance()->clearVariables(); changeStatus(Cantor::Session::Done); } @@ -136,9 +144,12 @@ return nullptr; } -QSyntaxHighlighter *JuliaSession::syntaxHighlighter(QObject* parent) +QSyntaxHighlighter *JuliaSession::syntaxHighlighter(QObject *parent) { JuliaHighlighter *highlighter = new JuliaHighlighter(parent); + QObject::connect( + this, SIGNAL(updateHighlighter()), highlighter, SLOT(updateHighlight()) + ); return highlighter; } @@ -169,6 +180,8 @@ } m_runningExpressions.removeAll(m_currentExpression); + listVariables(); + changeStatus(Cantor::Session::Done); } @@ -178,7 +191,7 @@ runJuliaCommandAsync(expr->command()); } -QString JuliaSession::getStringFromServer(const QString& method) +QString JuliaSession::getStringFromServer(const QString &method) { const QDBusReply &reply = m_interface->call(method); if (reply.isValid()) { @@ -197,4 +210,92 @@ return getStringFromServer(QLatin1String("getError")); } +void JuliaSession::listVariables() +{ + QStringList ignoredVariables; + ignoredVariables // These are tech variables of juliaserver + << QLatin1String("__originalSTDOUT__") + << QLatin1String("__originalSTDERR__"); + + auto rem_marker = QString::fromLatin1("\"%1\"") + .arg(JuliaVariableManagementExtension::REMOVED_VARIABLE_MARKER); + + JuliaKeywords::instance()->clearVariables(); + JuliaKeywords::instance()->clearFunctions(); + + QStringList processed_modules; + QStringList modules_to_process; + modules_to_process << QLatin1String("__GLOBAL__"); + + while (modules_to_process.size() > 0) { + auto module = modules_to_process.front(); + modules_to_process.pop_front(); + if (processed_modules.contains(module)) { + continue; + } + processed_modules << module; + + 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(); + } else { + whos_output = it.value(); + } + } + + foreach (QString line, whos_output.split(QLatin1String("\n"))) { + QString name = + line.simplified().split(QLatin1String(" ")).first().simplified(); + QString type = + line.simplified().split(QLatin1String(" ")).last().simplified(); + + if (ignoredVariables.contains(name)) { + continue; + } + + if (type == QLatin1String("Module")) { + modules_to_process.append(name); + continue; + } + + if (type == QLatin1String("Function")) { + JuliaKeywords::instance()->addFunction(name); + continue; + } + + if (module != QLatin1String("__GLOBAL__")) { + continue; // Don't add variables not included on global scope + } + + runJuliaCommand(QString::fromLatin1("show(%1)").arg(name)); + QString value = getOutput().simplified(); + + if (type == QLatin1String("ASCIIString")) { + if (value == rem_marker) { + m_variableModel->removeVariable(name); + continue; + } + } + + if (not name.isEmpty()) { + m_variableModel->addVariable(name, value); + JuliaKeywords::instance()->addVariable(name); + } + } + } + + emit updateHighlighter(); +} + +QAbstractItemModel *JuliaSession::variableModel() +{ + return m_variableModel; +} + #include "juliasession.moc" diff --git a/src/backends/julia/juliautils.h b/src/backends/julia/juliautils.h new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliautils.h @@ -0,0 +1,29 @@ +/* + 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 + +inline QString fromSource(const QString &resourceName) +{ + QFile text(resourceName); + text.open(QIODevice::ReadOnly); + return QString::fromLatin1(text.readAll()); +} diff --git a/src/backends/julia/variables_cleaner.jl b/src/backends/julia/variables_cleaner.jl new file mode 100644 --- /dev/null +++ b/src/backends/julia/variables_cleaner.jl @@ -0,0 +1,7 @@ +# Variable cleaning script +for name in names(Main)[4:end] + if name == :__originalSTDOUT__ || name == :__originalSTDERR__ + continue + end + @eval (($name) = "%1") +end diff --git a/src/backends/julia/variables_loader.jl b/src/backends/julia/variables_loader.jl new file mode 100644 --- /dev/null +++ b/src/backends/julia/variables_loader.jl @@ -0,0 +1,6 @@ +# Variable loading script +using JLD +for (var_name, value) in load("/home/dener/test") + s = symbol(var_name) + @eval (($s) = ($value)) +end diff --git a/src/backends/julia/variables_saver.jl b/src/backends/julia/variables_saver.jl new file mode 100644 --- /dev/null +++ b/src/backends/julia/variables_saver.jl @@ -0,0 +1,14 @@ +# Variable saving script +using JLD +jldopen("%1", "w") do file + for name in names(Main)[4:end] + if name == :__originalSTDOUT__ || name == :__originalSTDERR__ + continue + end + var_info = summary(eval(name)) + if var_info == "Function" || var_info == "Module" + continue + end + write(file, repr(name)[2:end], eval(name)) + end +end