diff --git a/src/backends/python/pythonhighlighter.cpp b/src/backends/python/pythonhighlighter.cpp index 9c8482fb..4d17d002 100644 --- a/src/backends/python/pythonhighlighter.cpp +++ b/src/backends/python/pythonhighlighter.cpp @@ -1,156 +1,162 @@ /* 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) 2013 Filipe Saraiva */ #include "pythonhighlighter.h" #include "pythonkeywords.h" #include #include -PythonHighlighter::PythonHighlighter(QObject* parent) : Cantor::DefaultHighlighter(parent) +PythonHighlighter::PythonHighlighter(QObject* parent, const int pythonVersion) : Cantor::DefaultHighlighter(parent) { qDebug() << "PythonHighlighter constructor"; addRule(QRegExp(QLatin1String("\\b\\w+(?=\\()")), functionFormat()); //Code highlighting the different keywords addKeywords(PythonKeywords::instance()->keywords()); addFunctions(PythonKeywords::instance()->functions()); addVariables(PythonKeywords::instance()->variables()); + + if (pythonVersion == 2) + { + removeRule(QLatin1String("print")); + addRule(QLatin1String("print"), keywordFormat()); + } } void PythonHighlighter::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_SMALL_QUOTE_STRING = 2; const int IN_SINGLE_QUOTE_STRING = 4; const int IN_TRIPLE_QUOTE_STRING = 8; QRegExp multiLineCommentStartEnd(QLatin1String("'''")); QRegExp smallQuoteStartEnd(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_SMALL_QUOTE_STRING, IN_MULTILINE_COMMENT }; QList regexps = { tripleQuoteStringStartEnd, singleQuoteStringStartEnd, smallQuoteStartEnd, multiLineCommentStartEnd }; 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() && !triggered; i++) { int flag = flags[i]; QRegExp ®exp = regexps[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.size(); i++) { QRegExp ®exp = regexps[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 && 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 PythonHighlighter::updateHighlight() { addVariables(m_variables); rehighlight(); } void PythonHighlighter::addVariable(const QString variable) { m_variables << variable; } void PythonHighlighter::clearVariables() { removeRules(m_variables); m_variables.clear(); rehighlight(); } diff --git a/src/backends/python/pythonhighlighter.h b/src/backends/python/pythonhighlighter.h index 8085de8e..56d86d76 100644 --- a/src/backends/python/pythonhighlighter.h +++ b/src/backends/python/pythonhighlighter.h @@ -1,49 +1,49 @@ /* 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) 2013 Filipe Saraiva */ #ifndef _PYTHONHIGHLIGHTER_H #define _PYTHONHIGHLIGHTER_H #include "defaulthighlighter.h" class PythonHighlighter : public Cantor::DefaultHighlighter { Q_OBJECT public: - explicit PythonHighlighter(QObject* parent); + explicit PythonHighlighter(QObject* parent, const int pythonVersion); ~PythonHighlighter() override = default; public Q_SLOTS: void updateHighlight(); void addVariable(const QString variable); void clearVariables(); protected: void highlightBlock(const QString& text) override; private: QRegExp commentStartExpression; QRegExp commentEndExpression; QStringList m_variables; }; #endif /* _PYTHONHIGHLIGHTER_H */ diff --git a/src/backends/python/pythonkeywords.cpp b/src/backends/python/pythonkeywords.cpp index db46b6d2..b84cc58c 100644 --- a/src/backends/python/pythonkeywords.cpp +++ b/src/backends/python/pythonkeywords.cpp @@ -1,108 +1,98 @@ /* 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) 2011 Filipe Saraiva */ #include "pythonkeywords.h" #include #include #include #include PythonKeywords::PythonKeywords() { qDebug() << "PythonKeywords constructor"; } PythonKeywords* PythonKeywords::instance() { static PythonKeywords* inst = nullptr; if(inst == nullptr) { inst = new PythonKeywords(); inst->loadKeywords(); } return inst; } void PythonKeywords::loadKeywords() { KSyntaxHighlighting::Repository m_repository; KSyntaxHighlighting::Definition definition = m_repository.definitionForName(QLatin1String("Python")); m_keywords << definition.keywordList(QLatin1String("import")); m_keywords << definition.keywordList(QLatin1String("defs")); m_keywords << definition.keywordList(QLatin1String("operators")); m_keywords << definition.keywordList(QLatin1String("flow")); m_functions << definition.keywordList(QLatin1String("builtinfuncs")); m_functions << definition.keywordList(QLatin1String("overloaders")); m_variables << definition.keywordList(QLatin1String("specialvars")); // We use qBinarySearch in PythonCompletetionObject for type fetching qSort(m_keywords); qSort(m_functions); qSort(m_variables); } void PythonKeywords::loadFromModule(const QString& module, const QStringList& keywords) { qDebug() << "Module imported" << module; if (module.isEmpty()){ for(int contKeyword = 0; contKeyword < keywords.size(); contKeyword++){ m_functions << keywords.at(contKeyword); } } else { m_variables << module; for(int contKeyword = 0; contKeyword < keywords.size(); contKeyword++){ m_functions << module + QLatin1String(".") + keywords.at(contKeyword); } } } -void PythonKeywords::python2Mode() -{ - // In Python 2 print is keyword, not function - m_functions.removeOne(QLatin1String("print")); - m_keywords << QLatin1String("print"); - - qSort(m_keywords); - qSort(m_functions); -} - const QStringList& PythonKeywords::variables() const { return m_variables; } const QStringList& PythonKeywords::functions() const { return m_functions; } const QStringList& PythonKeywords::keywords() const { return m_keywords; } diff --git a/src/backends/python/pythonkeywords.h b/src/backends/python/pythonkeywords.h index 242ae662..72abccd6 100644 --- a/src/backends/python/pythonkeywords.h +++ b/src/backends/python/pythonkeywords.h @@ -1,50 +1,48 @@ /* 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) 2013 Filipe Saraiva */ #ifndef _PYTHONKEYWORDS_H #define _PYTHONKEYWORDS_H #include class PythonKeywords { private: PythonKeywords(); ~PythonKeywords() = default; public: static PythonKeywords* instance(); const QStringList& functions() const; const QStringList& keywords() const; const QStringList& variables() const; void loadFromModule(const QString& module, const QStringList& keywords); - void python2Mode(); - private: void loadKeywords(); private: QStringList m_functions; QStringList m_keywords; QStringList m_variables; }; #endif /* _PYTHONKEYWORDS_H */ diff --git a/src/backends/python/pythonsession.cpp b/src/backends/python/pythonsession.cpp index f0e51051..52d1381d 100644 --- a/src/backends/python/pythonsession.cpp +++ b/src/backends/python/pythonsession.cpp @@ -1,431 +1,430 @@ /* 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) 2012 Filipe Saraiva Copyright (C) 2015 Minh Ngo */ #include "pythonsession.h" #include "pythonexpression.h" #include "pythonhighlighter.h" #include "pythoncompletionobject.h" #include "pythonkeywords.h" #include "pythonutils.h" #include #include #include #include #include #include #include #include #include PythonSession::PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName, const QString DbusChannelName) : Session(backend) , m_variableModel(new Cantor::DefaultVariableModel(this)) , m_currentExpression(nullptr) , m_pIface(nullptr) , m_pProcess(nullptr) , serverName(serverName) , DbusChannelName(DbusChannelName) + , m_pythonVersion(pythonVersion) { - if (pythonVersion == 2) - PythonKeywords::instance()->python2Mode(); } void PythonSession::login() { qDebug()<<"login"; emit loginStarted(); // TODO: T6113, T6114 if (m_pProcess) m_pProcess->deleteLater(); m_pProcess = new KProcess(this); m_pProcess->setOutputChannelMode(KProcess::SeparateChannels); (*m_pProcess) << QStandardPaths::findExecutable(serverName); m_pProcess->start(); m_pProcess->waitForStarted(); m_pProcess->waitForReadyRead(); QTextStream stream(m_pProcess->readAllStandardOutput()); const QString& readyStatus = QString::fromLatin1("ready"); while (m_pProcess->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 = DbusChannelName + QString::fromLatin1("-%1").arg(m_pProcess->pid()); m_pIface = new QDBusInterface(serviceName, QString::fromLatin1("/"), QString(), QDBusConnection::sessionBus()); if (!m_pIface->isValid()) { qWarning() << QDBusConnection::sessionBus().lastError().message(); return; } m_pIface->call(QString::fromLatin1("login")); m_pIface->call(QString::fromLatin1("setFilePath"), worksheetPath); const QStringList& scripts = autorunScripts(); if(!scripts.isEmpty()){ QString autorunScripts = scripts.join(QLatin1String("\n")); getPythonCommandOutput(autorunScripts); } const QString& importerFile = QLatin1String(":py/import_default_modules.py"); evaluateExpression(fromSource(importerFile), Cantor::Expression::DeleteOnFinish, true); listVariables(); changeStatus(Session::Done); emit loginDone(); } void PythonSession::logout() { // TODO: T6113, T6114 m_pProcess->terminate(); m_variableModel->clearVariables(); emit clearVariables(); qDebug()<<"logout"; changeStatus(Status::Disable); } void PythonSession::interrupt() { // TODO: T6113, T6114 if (m_pProcess->pid()) m_pProcess->kill(); qDebug()<<"interrupt"; foreach(Cantor::Expression* e, m_runningExpressions) e->interrupt(); m_runningExpressions.clear(); changeStatus(Cantor::Session::Done); } Cantor::Expression* PythonSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal) { qDebug() << "evaluating: " << cmd; PythonExpression* expr = new PythonExpression(this, internal); changeStatus(Cantor::Session::Running); expr->setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void PythonSession::runExpression(PythonExpression* expr) { qDebug() << "run expression"; m_currentExpression = expr; const QString& command = expr->command(); QStringList commandLine = command.split(QLatin1String("\n")); QString commandProcessing; for(const QString& command : commandLine){ const QString firstLineWord = command.trimmed().replace(QLatin1String("("), QLatin1String(" ")) .split(QLatin1String(" ")).at(0); // Ignore comments if (firstLineWord.length() != 0 && firstLineWord[0] == QLatin1Char('#')){ commandProcessing += command + QLatin1String("\n"); continue; } if(firstLineWord.contains(QLatin1String("execfile"))){ commandProcessing += command; continue; } commandProcessing += command + QLatin1String("\n"); } readExpressionOutput(commandProcessing); } // Is called asynchronously in the Python3 plugin void PythonSession::readExpressionOutput(const QString& commandProcessing) { readOutput(commandProcessing); } void PythonSession::getPythonCommandOutput(const QString& commandProcessing) { runPythonCommand(commandProcessing); m_output = getOutput(); m_error = getError(); } bool PythonSession::identifyKeywords(const QString& command) { QString verifyErrorImport; QString listKeywords; QString keywordsString; QString moduleImported; QString moduleVariable; getPythonCommandOutput(command); qDebug() << "verifyErrorImport: "; if(!m_error.isEmpty()){ qDebug() << "returned false"; return false; } moduleImported += identifyPythonModule(command); moduleVariable += identifyVariableModule(command); if((moduleVariable.isEmpty()) && (!command.endsWith(QLatin1String("*")))){ keywordsString = command.section(QLatin1String(" "), 3).remove(QLatin1String(" ")); } if(moduleVariable.isEmpty() && (command.endsWith(QLatin1String("*")))){ listKeywords += QString::fromLatin1("import %1\n" \ "print(dir(%1))\n" \ "del %1\n").arg(moduleImported); } if(!moduleVariable.isEmpty()){ listKeywords += QLatin1String("print(dir(") + moduleVariable + QLatin1String("))\n"); } if(!listKeywords.isEmpty()){ getPythonCommandOutput(listKeywords); keywordsString = m_output; keywordsString.remove(QLatin1String("'")); keywordsString.remove(QLatin1String(" ")); keywordsString.remove(QLatin1String("[")); keywordsString.remove(QLatin1String("]")); } QStringList keywordsList = keywordsString.split(QLatin1String(",")); PythonKeywords::instance()->loadFromModule(moduleVariable, keywordsList); qDebug() << "Module imported" << moduleImported; return true; } QString PythonSession::identifyPythonModule(const QString& command) const { QString module; if(command.contains(QLatin1String("import "))){ module = command.section(QLatin1String(" "), 1, 1); } qDebug() << "module identified" << module; return module; } QString PythonSession::identifyVariableModule(const QString& command) const { QString variable; if(command.contains(QLatin1String("import "))){ variable = command.section(QLatin1String(" "), 1, 1); } if((command.contains(QLatin1String("import "))) && (command.contains(QLatin1String(" as ")))){ variable = command.section(QLatin1String(" "), 3, 3); } if(command.contains(QLatin1String("from "))){ variable = QLatin1String(""); } qDebug() << "variable identified" << variable; return variable; } void PythonSession::expressionFinished() { qDebug()<< "finished"; PythonExpression* expression = qobject_cast(sender()); m_runningExpressions.removeAll(expression); qDebug() << "size: " << m_runningExpressions.size(); } void PythonSession::updateOutput() { if(m_error.isEmpty()){ m_currentExpression->parseOutput(m_output); qDebug() << "output: " << m_output; } else { m_currentExpression->parseError(m_error); qDebug() << "error: " << m_error; } listVariables(); changeStatus(Cantor::Session::Done); } void PythonSession::readOutput(const QString& commandProcessing) { qDebug() << "readOutput"; getPythonCommandOutput(commandProcessing); updateOutput(); } void PythonSession::listVariables() { const QString& listVariableCommand = QLatin1String( "try: \n" " import numpy \n" " __cantor_numpy_internal__ = numpy.get_printoptions()['threshold'] \n" " numpy.set_printoptions(threshold=100000000) \n" "except ModuleNotFoundError: \n" " pass \n" "print(globals()) \n" "try: \n" " import numpy \n" " numpy.set_printoptions(threshold=__cantor_numpy_internal__) \n" " del __cantor_numpy_internal__ \n" "except ModuleNotFoundError: \n" " pass \n" ); getPythonCommandOutput(listVariableCommand); qDebug() << m_output; m_output.remove(QLatin1String("{")); m_output.remove(QLatin1String("<")); m_output.remove(QLatin1String(">")); m_output.remove(QLatin1String("}")); foreach(QString line, m_output.split(QLatin1String(", '"))){ QStringList parts = line.simplified().split(QLatin1String(":")); const QString& first = parts.first(); const QString& last = parts.last(); if(!first.startsWith(QLatin1String("'__")) && !first.startsWith(QLatin1String("__")) && !first.startsWith(QLatin1String("CatchOutPythonBackend'")) && !first.startsWith(QLatin1String("errorPythonBackend'")) && !first.startsWith(QLatin1String("outputPythonBackend'")) && !last.startsWith(QLatin1String(" class ")) && !last.startsWith(QLatin1String(" function ")) && !last.startsWith(QLatin1String(" module '")) /*skip imported modules*/ ) { m_variableModel->addVariable(parts.first().remove(QLatin1String("'")).simplified(), parts.last().simplified()); emit newVariable(parts.first().remove(QLatin1String("'")).simplified()); } } emit updateHighlighter(); } QSyntaxHighlighter* PythonSession::syntaxHighlighter(QObject* parent) { - PythonHighlighter* highlighter = new PythonHighlighter(parent); + PythonHighlighter* highlighter = new PythonHighlighter(parent, m_pythonVersion); QObject::connect(this, SIGNAL(updateHighlighter()), highlighter, SLOT(updateHighlight())); QObject::connect(this, SIGNAL(newVariable(QString)), highlighter, SLOT(addVariable(QString))); connect(this, &PythonSession::clearVariables, highlighter, &PythonHighlighter::clearVariables); return highlighter; } Cantor::CompletionObject* PythonSession::completionFor(const QString& command, int index) { return new PythonCompletionObject(command, index, this); } QAbstractItemModel* PythonSession::variableModel() { return m_variableModel; } void PythonSession::runPythonCommand(const QString& command) const { // TODO: T6113, T6114 m_pIface->call(QString::fromLatin1("runPythonCommand"), command); } QString PythonSession::getOutput() const { // TODO: T6113, T6114 const QDBusReply& reply = m_pIface->call(QString::fromLatin1("getOutput")); if (reply.isValid()) return reply.value(); return reply.error().message(); } QString PythonSession::getError() const { // TODO: T6113, T6114 const QDBusReply& reply = m_pIface->call(QString::fromLatin1("getError")); if (reply.isValid()) return reply.value(); return reply.error().message(); } void PythonSession::setWorksheetPath(const QString& path) { worksheetPath = path; } diff --git a/src/backends/python/pythonsession.h b/src/backends/python/pythonsession.h index 013091b0..b2159960 100644 --- a/src/backends/python/pythonsession.h +++ b/src/backends/python/pythonsession.h @@ -1,105 +1,106 @@ /* 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) 2012 Filipe Saraiva Copyright (C) 2015 Minh Ngo */ #ifndef _PYTHONSESSION_H #define _PYTHONSESSION_H #include "session.h" #include #include namespace Cantor { class DefaultVariableModel; } class PythonExpression; class KDirWatch; class QDBusInterface; class KProcess; class CANTOR_PYTHONBACKEND_EXPORT PythonSession : public Cantor::Session { Q_OBJECT public: PythonSession(Cantor::Backend* backend, int pythonVersion, const QString serverName, const QString DbusChannelName); ~PythonSession() override = default; void login() override; void logout() override; void interrupt() override; void runExpression(PythonExpression* expr); Cantor::Expression* evaluateExpression(const QString& command, Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; Cantor::CompletionObject* completionFor(const QString& command, int index=-1) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; QAbstractItemModel* variableModel() override; void setWorksheetPath(const QString& path) override; virtual bool integratePlots() const = 0; virtual QStringList autorunScripts() const = 0; private: Cantor::DefaultVariableModel* m_variableModel; QList m_runningExpressions; PythonExpression* m_currentExpression; QDBusInterface* m_pIface; KProcess* m_pProcess; QString serverName; QString DbusChannelName; QString worksheetPath; + int m_pythonVersion; protected: QString m_output; QString m_error; private: void listVariables(); void getPythonCommandOutput(const QString& commandProcessing); QString identifyPythonModule(const QString& command) const; QString identifyVariableModule(const QString& command) const; bool identifyKeywords(const QString& command); void runPythonCommand(const QString& command) const; QString getOutput() const; QString getError() const; virtual void readExpressionOutput(const QString& commandProcessing); protected: void updateOutput(); private Q_SLOTS: void readOutput(const QString& commandProcessing); void expressionFinished(); Q_SIGNALS: void updateHighlighter(); void newVariable(const QString variable); void clearVariables(); }; #endif /* _PYTHONSESSION_H */