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 @@ -5,6 +5,8 @@ juliabackend.cpp juliasession.cpp juliaexpression.cpp + juliakeywords.cpp + juliahighlighter.cpp ) kconfig_add_kcfg_files(JuliaBackend_SRCS settings.kcfgc) @@ -15,3 +17,4 @@ 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 --- a/src/backends/julia/juliabackend.cpp +++ b/src/backends/julia/juliabackend.cpp @@ -43,7 +43,7 @@ Cantor::Backend::Capabilities JuliaBackend::capabilities() const { - return Cantor::Backend::Nothing; + return Cantor::Backend::SyntaxHighlighting; } QString JuliaBackend::description() const diff --git a/src/backends/julia/juliahighlighter.h b/src/backends/julia/juliahighlighter.h new file mode 100644 --- /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/juliahighlighter.cpp b/src/backends/julia/juliahighlighter.cpp new file mode 100644 --- /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/juliakeywords.h b/src/backends/julia/juliakeywords.h new file mode 100644 --- /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/juliakeywords.cpp b/src/backends/julia/juliakeywords.cpp new file mode 100644 --- /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/juliasession.h b/src/backends/julia/juliasession.h --- a/src/backends/julia/juliasession.h +++ b/src/backends/julia/juliasession.h @@ -45,6 +45,8 @@ const QString &cmd, int index = -1) override; + virtual QSyntaxHighlighter *syntaxHighlighter(QObject *parent); + private Q_SLOTS: void onResultReady(); 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 @@ -27,6 +27,7 @@ #include "juliaexpression.h" #include "settings.h" +#include "juliahighlighter.h" JuliaSession::JuliaSession(Cantor::Backend *backend) : Session(backend) @@ -135,6 +136,12 @@ 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); diff --git a/src/backends/julia/keywords.xml b/src/backends/julia/keywords.xml new file mode 100644 --- /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 + +