diff --git a/src/backends/julia/CMakeLists.txt b/src/backends/julia/CMakeLists.txt index c7d4dc25..760a20ab 100644 --- a/src/backends/julia/CMakeLists.txt +++ b/src/backends/julia/CMakeLists.txt @@ -1,17 +1,20 @@ add_subdirectory(juliaserver) add_subdirectory(tests) set(JuliaBackend_SRCS juliabackend.cpp juliasession.cpp juliaexpression.cpp + juliakeywords.cpp + juliahighlighter.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 Qt5::DBus) install(FILES juliabackend.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) +install(FILES keywords.xml DESTINATION ${KDE_INSTALL_DATADIR}/cantor/juliabackend) diff --git a/src/backends/julia/juliabackend.cpp b/src/backends/julia/juliabackend.cpp index 86679ef6..a0eff9d6 100644 --- a/src/backends/julia/juliabackend.cpp +++ b/src/backends/julia/juliabackend.cpp @@ -1,95 +1,95 @@ /* 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" JuliaBackend::JuliaBackend(QObject *parent, const QList &args) : Cantor::Backend(parent, args) { setEnabled(true); } QString JuliaBackend::id() const { return QLatin1String("julia"); } Cantor::Session *JuliaBackend::createSession() { return new JuliaSession(this); } Cantor::Backend::Capabilities JuliaBackend::capabilities() const { - return Cantor::Backend::Nothing; + return Cantor::Backend::SyntaxHighlighting; } 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/juliahighlighter.cpp b/src/backends/julia/juliahighlighter.cpp new file mode 100644 index 00000000..65b8aaa4 --- /dev/null +++ b/src/backends/julia/juliahighlighter.cpp @@ -0,0 +1,155 @@ +/* + * 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 + +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()); +} + +void JuliaHighlighter::highlightBlock(const QString &text) +{ + if (skipHighlighting(text)) { + return; + } + + // Do some backend independent highlighting (brackets etc.) + DefaultHighlighter::highlightBlock(text); + + 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; + + QRegExp multiLineCommentStart(QLatin1String("#=")); + QRegExp multiLineCommentEnd(QLatin1String("=#")); + QRegExp characterStartEnd(QLatin1String("'")); + QRegExp singleQuoteStringStartEnd(QLatin1String("\"")); + QRegExp tripleQuoteStringStartEnd(QLatin1String("\"\"\"")); + QRegExp singleLineCommentStart(QLatin1String("#(?!=)")); + + int state = previousBlockState(); + if (state == -1) { + state = 0; + } + + 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; + while (pos < text.length()) { + // Trying to close current environments + bool triggered = false; + for (int i = 0; i < flags.size() and not triggered; i++) { + int flag = flags[i]; + QRegExp ®exp = regexps_ends[i]; + QTextCharFormat &format = formats[i]; + if (state & flag) { + int new_pos = regexp.indexIn(text, pos); + int length; + if (new_pos == -1) { + length = text.length() - pos; + } else { + length = new_pos - pos + regexp.matchedLength(); + state -= flag; + } + setFormat(pos, length, format); + pos = pos + length; + triggered = true; + } + } + if (triggered) { + continue; + } + + QRegExp *minRegexp = nullptr; + int minPos = INT_MAX; + int minIdx = -1; + 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; + } + } + + int singleLineCommentStartPos = + singleLineCommentStart.indexIn(text, pos); + + if (singleLineCommentStartPos != -1 + and singleLineCommentStartPos < minPos) { + setFormat(pos, text.length() - pos, commentFormat()); + break; + } else if (minRegexp) { + state += flags[minIdx]; + pos = minPos + minRegexp->matchedLength(); + setFormat(minPos, minRegexp->matchedLength(), formats[minIdx]); + } else { + break; + } + } + + setCurrentBlockState(state); +} + +void JuliaHighlighter::updateHighlight() +{ + addVariables(JuliaKeywords::instance()->variables()); + rehighlight(); +} + +QString JuliaHighlighter::nonSeparatingCharacters() const +{ + return QLatin1String("[\\w\\u00A1-\\uFFFF!]"); +} diff --git a/src/backends/julia/juliahighlighter.h b/src/backends/julia/juliahighlighter.h new file mode 100644 index 00000000..a112732c --- /dev/null +++ b/src/backends/julia/juliahighlighter.h @@ -0,0 +1,38 @@ +/* + * 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" + +class JuliaHighlighter: public Cantor::DefaultHighlighter +{ + Q_OBJECT + +public: + JuliaHighlighter(QObject *parent); + virtual ~JuliaHighlighter() {} + +public Q_SLOTS: + void updateHighlight(); + +protected: + virtual void highlightBlock(const QString &text) override; + virtual QString nonSeparatingCharacters() const override; +}; diff --git a/src/backends/julia/juliakeywords.cpp b/src/backends/julia/juliakeywords.cpp new file mode 100644 index 00000000..e15dbcaa --- /dev/null +++ b/src/backends/julia/juliakeywords.cpp @@ -0,0 +1,86 @@ +/* + * 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 "juliakeywords.h" + +#include +#include +#include +#include +#include + +JuliaKeywords *JuliaKeywords::instance() +{ + static JuliaKeywords *inst = 0; + if (inst == 0) { + inst = new JuliaKeywords(); + inst->loadFromFile(); + qSort(inst->m_keywords); + qSort(inst->m_variables); + } + + return inst; +} + +void JuliaKeywords::loadFromFile() +{ + //load the known keywords from an xml file + QFile file( + QStandardPaths::locate( + QStandardPaths::GenericDataLocation, + QLatin1String("cantor/juliabackend/keywords.xml") + ) + ); + + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "error opening keywords.xml file. highlighting and" + << "completion won't work"; + return; + } + + QXmlStreamReader xml(&file); + + xml.readNextStartElement(); + while (xml.readNextStartElement()) { + const QStringRef name = xml.name(); + + if (name == QLatin1String("keywords") + or name == QLatin1String("variables")) { + while (xml.readNextStartElement()) { + Q_ASSERT( + xml.isStartElement() and xml.name() == QLatin1String("word") + ); + + const QString text = xml.readElementText(); + + if (name == QLatin1String("keywords")) { + m_keywords << text; + } else if (name == QLatin1String("variables")) { + m_variables << text; + } + } + } else { + xml.skipCurrentElement(); + } + } + + if (xml.hasError()) { + qWarning() << "Error parsing keywords.xml:" << xml.errorString(); + } +} diff --git a/src/backends/julia/juliakeywords.h b/src/backends/julia/juliakeywords.h new file mode 100644 index 00000000..74f14b73 --- /dev/null +++ b/src/backends/julia/juliakeywords.h @@ -0,0 +1,40 @@ +/* + * 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 + +class JuliaKeywords +{ +public: + static JuliaKeywords *instance(); + + const QStringList &keywords() const { return m_keywords; } + const QStringList &variables() const { return m_variables; } + +private: + QStringList m_keywords; + QStringList m_variables; + + JuliaKeywords() {} + ~JuliaKeywords() {} + + void loadFromFile(); +}; diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp index e1f84b9c..50dfd421 100644 --- a/src/backends/julia/juliasession.cpp +++ b/src/backends/julia/juliasession.cpp @@ -1,191 +1,198 @@ /* 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 "juliaexpression.h" #include "settings.h" +#include "juliahighlighter.h" JuliaSession::JuliaSession(Cantor::Backend *backend) : Session(backend) , m_process(nullptr) , m_interface(nullptr) , m_currentExpression(nullptr) { } void JuliaSession::login() { 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 (not m_interface->isValid()) { qWarning() << QDBusConnection::sessionBus().lastError().message(); return; } m_interface->call( QString::fromLatin1("login"), JuliaSettings::self()->replPath().path() ); emit ready(); } void JuliaSession::logout() { m_process->terminate(); changeStatus(Cantor::Session::Done); } 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) { Q_UNUSED(command); Q_UNUSED(index); return nullptr; } +QSyntaxHighlighter *JuliaSession::syntaxHighlighter(QObject *parent) +{ + JuliaHighlighter *highlighter = new JuliaHighlighter(parent); + 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); 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() and reply.value(); } #include "juliasession.moc" diff --git a/src/backends/julia/juliasession.h b/src/backends/julia/juliasession.h index 9c8a40c1..dd2834df 100644 --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -1,69 +1,71 @@ /* 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 "session.h" class JuliaExpression; class KProcess; class QDBusInterface; class JuliaSession: public Cantor::Session { Q_OBJECT public: JuliaSession(Cantor::Backend *backend); virtual ~JuliaSession() {} virtual void login() override; virtual void logout() override; virtual void interrupt() override; virtual Cantor::Expression *evaluateExpression( const QString &command, Cantor::Expression::FinishingBehavior behave) override; virtual Cantor::CompletionObject *completionFor( const QString &cmd, int index = -1) override; + virtual QSyntaxHighlighter *syntaxHighlighter(QObject *parent); + private Q_SLOTS: void onResultReady(); private: KProcess *m_process; QDBusInterface *m_interface; QList m_runningExpressions; JuliaExpression *m_currentExpression; friend JuliaExpression; void runExpression(JuliaExpression *expression); void runJuliaCommand(const QString &command) const; void runJuliaCommandAsync(const QString &command); QString getStringFromServer(const QString &method); QString getOutput(); QString getError(); bool getWasException(); }; diff --git a/src/backends/julia/keywords.xml b/src/backends/julia/keywords.xml new file mode 100644 index 00000000..45fe08ad --- /dev/null +++ b/src/backends/julia/keywords.xml @@ -0,0 +1,44 @@ + + + + false + Inf + NaN + nothing + true + + + abstract + baremodule + begin + bitstype + break + catch + const + continue + do + elseif + else + end + export + finally + for + function + global + if + immutable + importall + import + let + local + macro + module + quote + return + try + typealias + type + using + while + +