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) @@ -18,3 +19,8 @@ install(FILES juliabackend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) install(FILES keywords.xml DESTINATION ${KDE_INSTALL_DATADIR}/cantor/juliabackend) +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 --- 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 "juliascriptloading.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 loadScript(QLatin1String("variables_cleaner")) + .arg(REMOVED_VARIABLE_MARKER); +} + +QString JuliaVariableManagementExtension::saveVariables(const QString &fileName) +{ + return loadScript(QLatin1String("variables_saver")).arg(fileName); +} + +QString JuliaVariableManagementExtension::loadVariables(const QString &fileName) +{ + return loadScript(QLatin1String("variables_loader")).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 @@ -27,11 +27,23 @@ 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 @@ -84,3 +84,29 @@ 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/juliascriptloading.h b/src/backends/julia/juliascriptloading.h new file mode 100644 --- /dev/null +++ b/src/backends/julia/juliascriptloading.h @@ -0,0 +1,37 @@ +/* + 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 + +inline QString loadScript(const QString &scriptName) +{ + QFile text( + QStandardPaths::locate( + QStandardPaths::GenericDataLocation, + QString::fromLatin1( + "cantor/juliabackend/scripts/%1.jl" + ).arg(scriptName) + ) + ); + text.open(QIODevice::ReadOnly); + return QString::fromLatin1(text.readAll()); +} 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() @@ -92,8 +92,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); @@ -68,4 +81,6 @@ QString getOutput(); QString getError(); bool getWasException(); + + 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 @@ -25,15 +25,21 @@ #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_interface(nullptr) , m_currentExpression(nullptr) + , m_variableModel(new Cantor::DefaultVariableModel(this)) { } @@ -89,12 +95,18 @@ JuliaSettings::self()->replPath().path() ); + listVariables(); + emit ready(); } void JuliaSession::logout() { m_process->terminate(); + + JuliaKeywords::instance()->clearVariables(); + JuliaKeywords::instance()->clearFunctions(); + changeStatus(Cantor::Session::Done); } @@ -139,6 +151,9 @@ QSyntaxHighlighter *JuliaSession::syntaxHighlighter(QObject *parent) { JuliaHighlighter *highlighter = new JuliaHighlighter(parent); + QObject::connect( + this, SIGNAL(updateHighlighter()), highlighter, SLOT(updateHighlight()) + ); return highlighter; } @@ -162,6 +177,8 @@ m_currentExpression->finalize(); m_runningExpressions.removeAll(m_currentExpression); + listVariables(); + changeStatus(Cantor::Session::Done); } @@ -195,4 +212,115 @@ return reply.isValid() and reply.value(); } +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(); + m_whos_cache[module] = whos_output; + } else { + whos_output = it.value(); + } + } + + 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()) { + continue; + } + + 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 + } + + batchCommands << QString::fromLatin1("show(%1);").arg(name); + batchTypes << type; + batchNames << name; + } + + if (batchCommands.isEmpty()) { + continue; + } + + 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) { + m_variableModel->removeVariable(name); + continue; + } + } + + 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/scripts/variables_cleaner.jl b/src/backends/julia/scripts/variables_cleaner.jl new file mode 100644 --- /dev/null +++ b/src/backends/julia/scripts/variables_cleaner.jl @@ -0,0 +1,11 @@ +# Variable cleaning script +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 + @eval (($name) = "%1") +end diff --git a/src/backends/julia/scripts/variables_loader.jl b/src/backends/julia/scripts/variables_loader.jl new file mode 100644 --- /dev/null +++ b/src/backends/julia/scripts/variables_loader.jl @@ -0,0 +1,6 @@ +# Variable loading script +import JLD +for (var_name, value) in JLD.load("%1") + s = symbol(var_name) + @eval (($s) = ($value)) +end diff --git a/src/backends/julia/scripts/variables_saver.jl b/src/backends/julia/scripts/variables_saver.jl new file mode 100644 --- /dev/null +++ b/src/backends/julia/scripts/variables_saver.jl @@ -0,0 +1,14 @@ +# Variable saving script +import JLD +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 + JLD.write(file, repr(name)[2:end], eval(name)) + end +end