diff --git a/src/backends/julia/CMakeLists.txt b/src/backends/julia/CMakeLists.txt index ebc7f20d..96c8caf2 100644 --- a/src/backends/julia/CMakeLists.txt +++ b/src/backends/julia/CMakeLists.txt @@ -1,41 +1,42 @@ include_directories(${JULIA_INCLUDE_DIRS}) add_subdirectory(juliaserver) set(JuliaBackend_SRCS juliabackend.cpp juliasession.cpp juliaexpression.cpp juliakeywords.cpp + juliavariablemodel.cpp juliahighlighter.cpp juliaextensions.cpp juliacompletionobject.cpp ) kconfig_add_kcfg_files(JuliaBackend_SRCS settings.kcfgc) ki18n_wrap_ui(JuliaBackend_SRCS settings.ui) add_backend(juliabackend ${JuliaBackend_SRCS}) target_link_libraries(cantor_juliabackend ${JULIA_LIBRARY} Qt5::DBus KF5::SyntaxHighlighting ) if(BUILD_TESTING) add_executable(testjulia testjulia.cpp) add_test(NAME testjulia COMMAND testjulia) - target_link_libraries(testjulia + target_link_libraries(testjulia Qt5::Test - cantorlibs + cantorlibs cantortest ) endif(BUILD_TESTING) install(FILES juliabackend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) 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 index 15510606..bd46f71d 100644 --- a/src/backends/julia/juliabackend.cpp +++ b/src/backends/julia/juliabackend.cpp @@ -1,109 +1,114 @@ /* 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 "juliabackend.h" #include #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); new JuliaPackagingExtension(this); new JuliaPlotExtension(this); new JuliaScriptExtension(this); new JuliaLinearAlgebraExtension(this); } QString JuliaBackend::id() const { return QLatin1String("julia"); } QString JuliaBackend::version() const { return QLatin1String("1.0.0"); } Cantor::Session *JuliaBackend::createSession() { return new JuliaSession(this); } Cantor::Backend::Capabilities JuliaBackend::capabilities() const { - return Cantor::Backend::SyntaxHighlighting | - Cantor::Backend::VariableManagement | - Cantor::Backend::Completion; + Cantor::Backend::Capabilities cap= + SyntaxHighlighting| + Completion; + + if (JuliaSettings::variableManagement()) + cap |= VariableManagement; + + return cap; } QString JuliaBackend::description() const { return i18n( "

Julia is a high-level, high-performance dynamic programming " "language for technical computing, with syntax that is familiar to " "users of other technical computing environments. It provides a " "sophisticated compiler, distributed parallel execution, numerical " "accuracy, and an extensive mathematical function library.

" ); } QUrl JuliaBackend::helpUrl() const { return QUrl(i18nc( "The url to the documentation of Julia, please check if there is a" " translated version and use the correct url", "http://docs.julialang.org/en/latest/" )); } bool JuliaBackend::requirementsFullfilled() const { return QFileInfo( JuliaSettings::self()->replPath().toLocalFile() ).isExecutable(); } QWidget *JuliaBackend::settingsWidget(QWidget *parent) const { QWidget *widget = new QWidget(parent); Ui::JuliaSettingsBase s; s.setupUi(widget); return widget; } KConfigSkeleton *JuliaBackend::config() const { return JuliaSettings::self(); } K_PLUGIN_FACTORY_WITH_JSON( juliabackend, "juliabackend.json", registerPlugin(); ) #include "juliabackend.moc" diff --git a/src/backends/julia/juliabackend.kcfg b/src/backends/julia/juliabackend.kcfg index fd281a33..11cfe1d1 100644 --- a/src/backends/julia/juliabackend.kcfg +++ b/src/backends/julia/juliabackend.kcfg @@ -1,28 +1,32 @@ QStandardPaths QUrl::fromLocalFile(QStandardPaths::findExecutable(QLatin1String("julia"))) + + + true + true svg diff --git a/src/backends/julia/juliahighlighter.cpp b/src/backends/julia/juliahighlighter.cpp index 8544c7be..c6fa84c8 100644 --- a/src/backends/julia/juliahighlighter.cpp +++ b/src/backends/julia/juliahighlighter.cpp @@ -1,185 +1,189 @@ /* * 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 "juliahighlighter.h" #include "juliakeywords.h" #include #include #include JuliaHighlighter::JuliaHighlighter(QObject *parent) : Cantor::DefaultHighlighter(parent) { addKeywords(JuliaKeywords::instance()->keywords()); addVariables(JuliaKeywords::instance()->variables()); addFunctions(JuliaKeywords::instance()->functions()); addFunctions(JuliaKeywords::instance()->plotShowingCommands()); } void JuliaHighlighter::highlightBlock(const QString &text) { if (skipHighlighting(text)) { return; } // Do some backend independent highlighting (brackets etc.) DefaultHighlighter::highlightBlock(text); // Now we are about to make correct strings and comments highlighting // // Main idea: as soon as string starts comment or anything else cant start // until current string ends. The same with comment, except '#' comment // that ends by newline // // To pass information to next block, we are using next states const int IN_MULTILINE_COMMENT = 1; const int IN_CHARACTER = 2; const int IN_SINGLE_QUOTE_STRING = 4; const int IN_TRIPLE_QUOTE_STRING = 8; // Markers of scopes start, ends QRegExp multiLineCommentStart(QLatin1String("#=")); QRegExp multiLineCommentEnd(QLatin1String("=#")); QRegExp characterStartEnd(QLatin1String("'")); QRegExp singleQuoteStringStartEnd(QLatin1String("\"")); QRegExp tripleQuoteStringStartEnd(QLatin1String("\"\"\"")); QRegExp singleLineCommentStart(QLatin1String("#(?!=)")); // Get current state int state = previousBlockState(); if (state == -1) { state = 0; } // This 4 arrays establish matching between state, start marker, end marker // and format to apply QList flags = { IN_TRIPLE_QUOTE_STRING, IN_SINGLE_QUOTE_STRING, IN_CHARACTER, IN_MULTILINE_COMMENT }; QList regexps_starts = { tripleQuoteStringStartEnd, singleQuoteStringStartEnd, characterStartEnd, multiLineCommentStart }; QList regexps_ends = { tripleQuoteStringStartEnd, singleQuoteStringStartEnd, characterStartEnd, multiLineCommentEnd }; QList formats = { stringFormat(), stringFormat(), stringFormat(), commentFormat() }; int pos = 0; // current position in block while (pos < text.length()) { // Trying to close current environments bool triggered = false; for (int i = 0; i < flags.size() && !triggered; i++) { int flag = flags[i]; QRegExp ®exp = regexps_ends[i]; QTextCharFormat &format = formats[i]; if (state & flag) { // Found current state // find where end marker is int new_pos = regexp.indexIn(text, pos); int length; if (new_pos == -1) { // not in this block, highlight till the end length = text.length() - pos; } else { // highlight untill the marker and modify state length = new_pos - pos + regexp.matchedLength(); state -= flag; } // Apply format to the found area setFormat(pos, length, format); pos = pos + length; triggered = true; } } if (triggered) { // We have done something move to next iteration continue; } // Now we should found the scope that start the closest to current // position QRegExp *minRegexp = nullptr; // closest marker int minPos = INT_MAX; // closest pos int minIdx = -1; // closest scope index for (int i = 0; i < regexps_starts.size(); i++) { QRegExp ®exp = regexps_starts[i]; int newPos = regexp.indexIn(text, pos); if (newPos != -1) { minPos = qMin(minPos, newPos); minRegexp = ®exp; minIdx = i; } } // Check where single line comment starts singleLineCommentStart.indexIn(text, pos); int singleLineCommentStartPos = singleLineCommentStart.pos(); if (singleLineCommentStartPos != -1 && singleLineCommentStartPos < minPos) { // single line comment starts earlier setFormat(singleLineCommentStartPos, text.length() - singleLineCommentStartPos, commentFormat()); break; } else if (minRegexp) { // We are going to another scope state += flags[minIdx]; pos = minPos + minRegexp->matchedLength(); setFormat(minPos, minRegexp->matchedLength(), formats[minIdx]); } else { // There is nothing to highlight break; } } setCurrentBlockState(state); } -void JuliaHighlighter::updateHighlight() +void JuliaHighlighter::addUserVariable(const QStringList& variables) { - // Remove rules for outdated variables and functions - for (const auto &var : JuliaKeywords::instance()->removedVariables()) { - removeRule(var); - } - for (const auto &func : JuliaKeywords::instance()->removedFunctions()) { - removeRule(func); - } + addVariables(variables); +} - // Add actual variables and function - addVariables(JuliaKeywords::instance()->variables()); - addFunctions(JuliaKeywords::instance()->functions()); - rehighlight(); +void JuliaHighlighter::removeUserVariable(const QStringList& variables) +{ + removeRules(variables); +} + +void JuliaHighlighter::addUserFunctions(const QStringList functions) +{ + addFunctions(functions); +} + +void JuliaHighlighter::removeUserFunctions(const QStringList functions) +{ + removeRules(functions); } QString JuliaHighlighter::nonSeparatingCharacters() const { return QLatin1String("[\\w¡-ﻼ!]"); } diff --git a/src/backends/julia/juliahighlighter.h b/src/backends/julia/juliahighlighter.h index a515906e..80bcb47a 100644 --- a/src/backends/julia/juliahighlighter.h +++ b/src/backends/julia/juliahighlighter.h @@ -1,60 +1,61 @@ /* * 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 "defaulthighlighter.h" /** * Implementation of JuliaHighlighter * * Takes into account loaded symbols from scope and predefined keywords. * There is no common regexps that bound to fail with such syntax-overloaded * languages as Julia */ class JuliaHighlighter: public Cantor::DefaultHighlighter { Q_OBJECT public: /** * Constructs JuliaHighlighter * * @param parent QObject parent */ explicit JuliaHighlighter(QObject *parent); ~JuliaHighlighter() override = default; public Q_SLOTS: - /** - * Call this to update highlighter to the current state of keywords storage - */ - void updateHighlight(); + void addUserVariable(const QStringList& variables); + void removeUserVariable(const QStringList& variables); + + void addUserFunctions(const QStringList functions); + void removeUserFunctions(const QStringList functions); protected: /** * @see Cantor::DefaultHighlighter::highlightBlock */ void highlightBlock(const QString &text) override; /** * @see Cantor::DefaultHighlighter::nonSeparatingCharacters */ QString nonSeparatingCharacters() const override; }; diff --git a/src/backends/julia/juliaserver/juliaserver.cpp b/src/backends/julia/juliaserver/juliaserver.cpp index 604696cf..685d2293 100644 --- a/src/backends/julia/juliaserver/juliaserver.cpp +++ b/src/backends/julia/juliaserver/juliaserver.cpp @@ -1,234 +1,242 @@ /* * 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 "juliaserver.h" #include #include #include #include #include #include #include QStringList JuliaServer::INTERNAL_VARIABLES = QStringList() << QLatin1String("__originalSTDOUT__") << QLatin1String("__originalSTDERR__"); JuliaServer::JuliaServer(QObject *parent) : QObject(parent), m_was_exception(false) { } JuliaServer::~JuliaServer() { jl_atexit_hook(0); } void JuliaServer::login(const QString &path) const { #if QT_VERSION_CHECK(JULIA_VERSION_MAJOR, JULIA_VERSION_MINOR, 0) >= QT_VERSION_CHECK(0, 6, 0) Q_UNUSED(path) jl_init(); #else QString dir_path = QFileInfo(path).dir().absolutePath(); jl_init(dir_path.toLatin1().constData()); #endif } #if QT_VERSION_CHECK(JULIA_VERSION_MAJOR, JULIA_VERSION_MINOR, 0) >= QT_VERSION_CHECK(0, 7, 0) #define JULIA_STDOUT "stdout" #define JULIA_STDERR "stderr" #else #define JULIA_STDOUT "STDOUT" #define JULIA_STDERR "STDOUT" #endif void JuliaServer::runJuliaCommand(const QString &command) { // Redirect stdout, stderr to temporary files QTemporaryFile output, error; if (!output.open() || !error.open()) { qFatal("Unable to create temporary files for stdout/stderr"); return; } jl_eval_string("const __originalSTDOUT__ = " JULIA_STDOUT); jl_eval_string("const __originalSTDERR__ = " JULIA_STDERR); jl_eval_string( QString::fromLatin1("redirect_stdout(open(\"%1\", \"w\"))") .arg(output.fileName()).toLatin1().constData() ); jl_eval_string( QString::fromLatin1("redirect_stderr(open(\"%1\", \"w\"))") .arg(error.fileName()).toLatin1().constData() ); // Run command jl_value_t *val = static_cast( jl_eval_string(command.toUtf8().constData()) ); if (jl_exception_occurred()) { // If exception occurred // Show it to user in stderr jl_value_t *ex = jl_exception_in_transit; jl_printf(JL_STDERR, "error during run:\n"); jl_function_t *showerror = jl_get_function(jl_base_module, "showerror"); jl_value_t *bt = static_cast( jl_eval_string("catch_backtrace()") ); jl_value_t *err_stream = static_cast( jl_eval_string(JULIA_STDERR) ); jl_call3(showerror, err_stream, ex, bt); jl_exception_clear(); m_was_exception = true; } else if (val) { // no exception occurred // If last result is not nothing, show it jl_function_t *equality = jl_get_function(jl_base_module, "=="); jl_value_t *nothing = static_cast(jl_eval_string("nothing")); bool is_nothing = jl_unbox_bool( static_cast(jl_call2(equality, nothing, val)) ); if (!is_nothing) { jl_value_t *out_display = static_cast(jl_eval_string("TextDisplay(" JULIA_STDOUT ")")); jl_function_t *display = jl_get_function(jl_base_module, "display"); jl_call2(display, out_display, val); } m_was_exception = false; } // Clean up streams and files jl_eval_string("flush(" JULIA_STDOUT ")"); jl_eval_string("flush(" JULIA_STDERR ")"); jl_eval_string("redirect_stdout(__originalSTDOUT__)"); jl_eval_string("redirect_stderr(__originalSTDERR__)"); // Clean up variables 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()); } QString JuliaServer::getError() const { return m_error; } QString JuliaServer::getOutput() const { return m_output; } bool JuliaServer::getWasException() const { return m_was_exception; } -void JuliaServer::parseModules() +void JuliaServer::parseModules(bool variableManagement) { - parseJlModule(jl_internal_main_module); + parseJlModule(jl_internal_main_module, variableManagement); } -void JuliaServer::parseJlModule(jl_module_t* module) +void JuliaServer::parseJlModule(jl_module_t* module, bool parseValue) { jl_function_t* jl_string_function = jl_get_function(jl_base_module, "string"); if (module != jl_internal_main_module) { const QString& moduleName = fromJuliaString(jl_call1(jl_string_function, (jl_value_t*)(module->name))); if (parsedModules.contains(moduleName)) return; else parsedModules.append(moduleName); } jl_function_t* jl_names_function = jl_get_function(jl_base_module, "names"); jl_value_t* names = jl_call1(jl_names_function, (jl_value_t*)module); jl_value_t **data = (jl_value_t**)jl_array_data(names); for (size_t i = 0; i < jl_array_len(names); i++) { bool isBindingResolved = (bool)jl_binding_resolved_p(module, (jl_sym_t*)(data[i])); if (isBindingResolved) { const QString& name = fromJuliaString(jl_call1(jl_string_function, data[i])); jl_value_t* value = jl_get_binding_or_error(module, (jl_sym_t*)(data[i]))->value; jl_datatype_t* datetype = (jl_datatype_t*)jl_typeof(value); QString type = QString::fromUtf8(jl_typeof_str(value)); // Module if (jl_is_module(value)) { if (module == jl_internal_main_module && (jl_module_t*)value != jl_internal_main_module) - parseJlModule((jl_module_t*)value); + parseJlModule((jl_module_t*)value, parseValue); } // Function else if (type.startsWith(QLatin1String("#")) || type == QLatin1String("Function")) { if (!m_functions.contains(name)) m_functions.append(name); } // Variable else if (datetype != jl_datatype_type) // Not type { if (module == jl_internal_main_module && !INTERNAL_VARIABLES.contains(name)) { - const QString& valueString = fromJuliaString(jl_call1(jl_string_function, value)); - if (m_variables.contains(name)) + if (parseValue) { - int i = m_variables.indexOf(name); - m_variableValues[i] = valueString; + const QString& valueString = fromJuliaString(jl_call1(jl_string_function, value)); + if (m_variables.contains(name)) + { + int i = m_variables.indexOf(name); + m_variableValues[i] = valueString; + } + else + { + m_variables.append(name); + m_variableValues.append(valueString); + } } else { - m_variables.append(name); - m_variableValues.append(valueString); + if (!m_variables.contains(name)) + m_variables.append(name); } } } } } } QString JuliaServer::fromJuliaString(const jl_value_t* value) { return QString::fromUtf8(jl_string_data(value)); } QStringList JuliaServer::variablesList() { return m_variables; } QStringList JuliaServer::variableValuesList() { return m_variableValues; } QStringList JuliaServer::functionsList() { return m_functions; } diff --git a/src/backends/julia/juliaserver/juliaserver.h b/src/backends/julia/juliaserver/juliaserver.h index 98d8f4d3..3c39d00f 100644 --- a/src/backends/julia/juliaserver/juliaserver.h +++ b/src/backends/julia/juliaserver/juliaserver.h @@ -1,109 +1,111 @@ /* * 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 #include #include #include /** * Implementation of command execution server with DBus interface for Julia * language. * * Uses Julia embedding * http://docs.julialang.org/en/release-0.4/manual/embedding/ to get results. */ class JuliaServer: public QObject { Q_OBJECT public: explicit JuliaServer(QObject *parent = nullptr); ~JuliaServer() override; public Q_SLOTS: /** * Initializer for JuliaServer. Call this first before using it * * @param path path to julia executable */ Q_SCRIPTABLE void login(const QString &path) const; /** * Runs a piece of julia code. After this returns use getOutput, getError, * getWasException methods to retrieve execution result. * * @param command maybe multiline piece of julia code to run */ Q_SCRIPTABLE void runJuliaCommand(const QString &command); /** * @return stdout output of the last command execution */ Q_SCRIPTABLE QString getOutput() const; /** * @return stderr output of the last command execution */ Q_SCRIPTABLE QString getError() const; /** * @return indicator that exception was triggered during last command * execution */ Q_SCRIPTABLE bool getWasException() const; /** * Reparse internal julia module and update list of variables and functions + * + * @param variableManagement true, if Variable Management enabled for this session */ - Q_SCRIPTABLE void parseModules(); + Q_SCRIPTABLE void parseModules(bool variableManagement); /** * @return list of variables in internal Julia's module */ Q_SCRIPTABLE QStringList variablesList(); /** * @return corresponding list of values for variables from variablesList. */ Q_SCRIPTABLE QStringList variableValuesList(); /** * @return list of function in internal Julia's module */ Q_SCRIPTABLE QStringList functionsList(); private: - void parseJlModule(jl_module_t* module); + void parseJlModule(jl_module_t* module, bool parseValue); QString fromJuliaString(const jl_value_t* value); private: QString m_error; //< Stores last stderr output QString m_output; //< Stores last stdout output bool m_was_exception; //< Stores indicator of exception QStringList parsedModules; QStringList m_variables; QStringList m_variableValues; QStringList m_functions; static QStringList INTERNAL_VARIABLES; }; diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp index 9098c009..aed0af54 100644 --- a/src/backends/julia/juliasession.cpp +++ b/src/backends/julia/juliasession.cpp @@ -1,286 +1,250 @@ /* 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 "juliavariablemodel.h" #include "juliaextensions.h" #include "juliabackend.h" #include "juliacompletionobject.h" #include -const QRegularExpression JuliaSession::typeVariableInfo = QRegularExpression(QLatin1String("\\w+\\[")); +using namespace Cantor; JuliaSession::JuliaSession(Cantor::Backend *backend) : Session(backend) , m_process(nullptr) , m_interface(nullptr) , m_currentExpression(nullptr) - , m_variableModel(new Cantor::DefaultVariableModel(this)) + , m_variableModel(new JuliaVariableModel(this)) + , m_needUpdate(false) { } 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(); + m_variableModel->setJuliaServer(m_interface); + m_variableModel->update(); // Plots integration if (integratePlots()) { runJuliaCommand( QLatin1String("import GR; ENV[\"GKS_WSTYPE\"] = \"nul\"") ); } changeStatus(Session::Done); emit loginDone(); qDebug() << "login to julia " << JULIA_VERSION_STRING << "done"; } void JuliaSession::logout() { m_process->terminate(); - JuliaKeywords::instance()->clearVariables(); - JuliaKeywords::instance()->clearFunctions(); m_variableModel->clearVariables(); - emit updateHighlighter(); changeStatus(Status::Disable); } 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, bool internal) { JuliaExpression *expr = new JuliaExpression(this, internal); 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()) - ); + + connect( m_variableModel, &DefaultVariableModel::variablesAdded, highlighter, &JuliaHighlighter::addUserVariable); + connect( m_variableModel, &DefaultVariableModel::variablesRemoved, highlighter, &JuliaHighlighter::removeUserVariable); + connect( m_variableModel, &JuliaVariableModel::functionsAdded, highlighter, &JuliaHighlighter::addUserFunctions); + connect( m_variableModel, &JuliaVariableModel::functionsRemoved, highlighter, &JuliaHighlighter::removeUserFunctions); + 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_needUpdate |= !m_currentExpression->isInternal(); m_runningExpressions.removeAll(m_currentExpression); - listVariables(); + if(m_needUpdate) + { + m_variableModel->update(); + m_needUpdate = false; + } changeStatus(Cantor::Session::Done); } void JuliaSession::runExpression(JuliaExpression *expr) { m_runningExpressions.append(expr); m_currentExpression = expr; runJuliaCommandAsync(expr->internalCommand()); } 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() -{ - JuliaKeywords::instance()->clearVariables(); - JuliaKeywords::instance()->clearFunctions(); - - m_interface->call(QLatin1String("parseModules")); - - const QStringList& variables = - static_cast>(m_interface->call(QLatin1String("variablesList"))).value(); - const QStringList& values = - static_cast>(m_interface->call(QLatin1String("variableValuesList"))).value(); - for (int i = 0; i < variables.size(); i++) - { - if (i >= values.size()) - { - qWarning() << "Don't have value for variable from julia server response, something wrong!"; - continue; - } - - const QString& name = variables[i]; - QString value = values[i]; - if (value != JuliaVariableManagementExtension::REMOVED_VARIABLE_MARKER) - { - // Register variable - // 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); - } - else - m_variableModel->removeVariable(name); - } - - const QStringList& functions = - static_cast>(m_interface->call(QLatin1String("functionsList"))).value(); - foreach (const QString& name, functions) - { - JuliaKeywords::instance()->addFunction(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 a6dab984..8200787b 100644 --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -1,175 +1,165 @@ /* 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 JuliaVariableModel; 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 */ explicit 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 = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) 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; + JuliaVariableModel *m_variableModel; + bool m_needUpdate; /// 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 occurred during the last command execution */ bool getWasException(); - - /** - * Updates variable model by querying all modules in scope with whos command - */ - void listVariables(); }; diff --git a/src/backends/julia/juliavariablemodel.cpp b/src/backends/julia/juliavariablemodel.cpp new file mode 100644 index 00000000..e4cdd638 --- /dev/null +++ b/src/backends/julia/juliavariablemodel.cpp @@ -0,0 +1,128 @@ +/* + 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 "juliavariablemodel.h" +#include "juliaextensions.h" +#include "juliasession.h" + +#include +#include +#include +#include + +#include "settings.h" + +using namespace Cantor; + +const QRegularExpression JuliaVariableModel::typeVariableInfo = QRegularExpression(QLatin1String("\\w+\\[")); + +JuliaVariableModel::JuliaVariableModel(JuliaSession* session): + DefaultVariableModel(session), + m_interface(nullptr) +{ +} + +void JuliaVariableModel::setJuliaServer(QDBusInterface* interface) +{ + m_interface = interface; +} + +void JuliaVariableModel::update() +{ + if (!m_interface) + return; + + m_interface->call(QLatin1String("parseModules"), JuliaSettings::variableManagement()); + + const QStringList& variables = + static_cast>(m_interface->call(QLatin1String("variablesList"))).value(); + + QList vars; + if (JuliaSettings::variableManagement()) + { + const QStringList& values = + static_cast>(m_interface->call(QLatin1String("variableValuesList"))).value(); + + for (int i = 0; i < variables.size(); i++) + { + if (i >= values.size()) + { + qWarning() << "Don't have value for variable from julia server response, something wrong!"; + continue; + } + + const QString& name = variables[i]; + QString value = values[i]; + if (value != JuliaVariableManagementExtension::REMOVED_VARIABLE_MARKER) + { + // Register variable + // 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 + vars << Variable{name, value.replace(typeVariableInfo,QLatin1String("["))}; + } + } + } + else + { + for (int i = 0; i < variables.size(); i++) + vars << Variable{variables[i], QString()}; + } + setVariables(vars); + + const QStringList& newFuncs = + static_cast>(m_interface->call(QLatin1String("functionsList"))).value(); + QStringList addedFuncs; + QStringList removedFuncs; + + //remove the old variables + int i = 0; + while (i < m_functions.size()) + { + //check if this var is present in the new variables + bool found=false; + for (const QString& func : newFuncs) + if(m_functions[i] == func) + { + found=true; + break; + } + + if(!found) + { + removedFuncs< +*/ + +#ifndef _PYTHONVARIABLEMODEL_H +#define _PYTHONVARIABLEMODEL_H + +#include + +#include "defaultvariablemodel.h" + +class JuliaSession; +class QDBusInterface; + +class JuliaVariableModel : public Cantor::DefaultVariableModel +{ + Q_OBJECT + public: + JuliaVariableModel( JuliaSession* session); + ~JuliaVariableModel() override = default; + + void update() override; + + void setJuliaServer(QDBusInterface* interface); + + Q_SIGNALS: + void functionsAdded(const QStringList funcs); + void functionsRemoved(const QStringList funcs); + + private: + static const QRegularExpression typeVariableInfo; + + private: + QDBusInterface* m_interface; + QStringList m_functions; +}; + +#endif /* _PYTHONVARIABLEMODEL_H */ diff --git a/src/backends/julia/settings.ui b/src/backends/julia/settings.ui index f7026c7c..8813485c 100644 --- a/src/backends/julia/settings.ui +++ b/src/backends/julia/settings.ui @@ -1,81 +1,91 @@ JuliaSettingsBase Path to Julia REPL: + + + + + Let Cantor follow the creation/destruction of variables + + + Enable Variable Management + + Integrate Plots in Worksheet (start a new session when changed) Inline Plots Intermediate Format: svg eps png Qt::Vertical 20 40 KUrlRequester QFrame
kurlrequester.h