diff --git a/src/backends/python/CMakeLists.txt b/src/backends/python/CMakeLists.txt index 2d10bf67..773d9b07 100644 --- a/src/backends/python/CMakeLists.txt +++ b/src/backends/python/CMakeLists.txt @@ -1,28 +1,29 @@ set( PythonBackend_SRCS pythonbackend.cpp pythonsession.cpp pythonexpression.cpp pythonkeywords.cpp + pythonvariablemodel.cpp pythonhighlighter.cpp pythoncompletionobject.cpp pythonextensions.cpp ) qt5_add_resources(PythonBackend_RSCS python.qrc) ki18n_wrap_ui(PythonBackend_SRCS settings.ui) add_library(cantor_pythonbackend SHARED ${PythonBackend_SRCS} ${PythonBackend_RSCS}) generate_export_header(cantor_pythonbackend) target_link_libraries(cantor_pythonbackend cantorlibs KF5::KIOCore KF5::ConfigCore KF5::ConfigGui KF5::SyntaxHighlighting Qt5::DBus ) install(TARGETS cantor_pythonbackend DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) #install(DIRECTORY . DESTINATION ${KDE_INSTALL_DATADIR}/cantor/pythonbackend FILES_MATCHING PATTERN "*.py") diff --git a/src/backends/python/pythoncompletionobject.cpp b/src/backends/python/pythoncompletionobject.cpp index 4e4bd099..eba71ad4 100644 --- a/src/backends/python/pythoncompletionobject.cpp +++ b/src/backends/python/pythoncompletionobject.cpp @@ -1,169 +1,170 @@ /* 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 "pythoncompletionobject.h" #include #include "result.h" #include "pythonsession.h" #include "pythonkeywords.h" PythonCompletionObject::PythonCompletionObject(const QString& command, int index, PythonSession* session) : Cantor::CompletionObject(session), m_expression(nullptr) { setLine(command, index); } void PythonCompletionObject::fetchCompletions() { if (session()->status() == Cantor::Session::Disable) { QStringList allCompletions; allCompletions << PythonKeywords::instance()->variables(); allCompletions << PythonKeywords::instance()->functions(); allCompletions << PythonKeywords::instance()->keywords(); setCompletions(allCompletions); emit fetchingDone(); } else { if (m_expression) return; qDebug() << "run fetchCompletions"; const QString& expr = QString::fromLatin1( "from __main__ import __dict__;" "from rlcompleter import Completer;" "print('|'.join(Completer(__dict__).global_matches('%1')+Completer(__dict__).attr_matches('%1')))" ).arg(command()); m_expression = session()->evaluateExpression(expr, Cantor::Expression::FinishingBehavior::DoNotDelete, true); // TODO: Python exec the expression before connect, so manualy run handler. Uncomment the connection after removing DBus // connect(m_expression, &Cantor::Expression::statusChanged, this, &PythonCompletionObject::extractCompletions); extractCompletions(m_expression->status()); } } void PythonCompletionObject::fetchIdentifierType() { if (session()->status() == Cantor::Session::Disable) { if (qBinaryFind(PythonKeywords::instance()->functions().begin(), PythonKeywords::instance()->functions().end(), identifier()) != PythonKeywords::instance()->functions().end()) emit fetchingTypeDone(FunctionType); else if (qBinaryFind(PythonKeywords::instance()->keywords().begin(), PythonKeywords::instance()->keywords().end(), identifier()) != PythonKeywords::instance()->keywords().end()) emit fetchingTypeDone(KeywordType); else emit fetchingTypeDone(VariableType); } else { if (m_expression) return; const QString& expr = QString::fromLatin1("callable(%1)").arg(identifier()); m_expression = session()->evaluateExpression(expr, Cantor::Expression::FinishingBehavior::DoNotDelete, true); // TODO: Python exec the expression before connect, so manualy run handler. Uncomment the connection after removing DBus // connect(m_expression, &Cantor::Expression::statusChanged, this, &PythonCompletionObject::extractIdentifierType); extractIdentifierType(m_expression->status()); } } void PythonCompletionObject::extractCompletions(Cantor::Expression::Status status) { if (!m_expression) return; + switch(status) { case Cantor::Expression::Error: qDebug() << "Error with PythonCompletionObject" << (m_expression->result() ? m_expression->result()->toHtml() : QLatin1String("extractCompletions")); break; case Cantor::Expression::Interrupted: qDebug() << "PythonCompletionObject was interrupted"; break; case Cantor::Expression::Done: if (m_expression->result()) setCompletions(m_expression->result()->toHtml().remove(QLatin1Char('(')).split(QLatin1Char('|'))); break; default: return; } m_expression->deleteLater(); m_expression = nullptr; emit fetchingDone(); } void PythonCompletionObject::extractIdentifierType(Cantor::Expression::Status status) { if (!m_expression) return; switch(status) { case Cantor::Expression::Error: if (m_expression->errorMessage().contains(QLatin1String("SyntaxError: invalid syntax"))) emit fetchingTypeDone(KeywordType); else qDebug() << "Error with PythonCompletionObject" << (m_expression->result() ? m_expression->result()->toHtml() : QLatin1String("extractIdentifierType")); break; case Cantor::Expression::Interrupted: qDebug() << "PythonCompletionObject was interrupted"; break; case Cantor::Expression::Done: if (m_expression->result()) { if (m_expression->result()) { if (m_expression->result()->toHtml() == QLatin1String("True")) emit fetchingTypeDone(FunctionType); else emit fetchingTypeDone(VariableType); } } break; default: return; } m_expression->deleteLater(); m_expression = nullptr; } bool PythonCompletionObject::mayIdentifierContain(QChar c) const { return c.isLetter() || c.isDigit() || c == QLatin1Char('_') || c == QLatin1Char('%') || c == QLatin1Char('$') || c == QLatin1Char('.'); } bool PythonCompletionObject::mayIdentifierBeginWith(QChar c) const { return c.isLetter() || c == QLatin1Char('_') || c == QLatin1Char('%') || c == QLatin1Char('$'); } diff --git a/src/backends/python/pythonhighlighter.cpp b/src/backends/python/pythonhighlighter.cpp index 4d17d002..4ac7d189 100644 --- a/src/backends/python/pythonhighlighter.cpp +++ b/src/backends/python/pythonhighlighter.cpp @@ -1,162 +1,154 @@ /* 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, 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() +void PythonHighlighter::addUserVariable(const QStringList& variables) { - addVariables(m_variables); - rehighlight(); + addVariables(variables); } -void PythonHighlighter::addVariable(const QString variable) +void PythonHighlighter::removeUserVariable(const QStringList& variables) { - m_variables << variable; -} - -void PythonHighlighter::clearVariables() -{ - removeRules(m_variables); - m_variables.clear(); - rehighlight(); + removeRules(variables); } diff --git a/src/backends/python/pythonhighlighter.h b/src/backends/python/pythonhighlighter.h index 56d86d76..9217545d 100644 --- a/src/backends/python/pythonhighlighter.h +++ b/src/backends/python/pythonhighlighter.h @@ -1,49 +1,46 @@ /* 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, const int pythonVersion); ~PythonHighlighter() override = default; public Q_SLOTS: - void updateHighlight(); - void addVariable(const QString variable); - void clearVariables(); + void addUserVariable(const QStringList& variables); + void removeUserVariable(const QStringList& variables); protected: void highlightBlock(const QString& text) override; private: QRegExp commentStartExpression; QRegExp commentEndExpression; - QStringList m_variables; - }; #endif /* _PYTHONHIGHLIGHTER_H */ diff --git a/src/backends/python/pythonserver.cpp b/src/backends/python/pythonserver.cpp index 8e150871..07c5eccc 100644 --- a/src/backends/python/pythonserver.cpp +++ b/src/backends/python/pythonserver.cpp @@ -1,147 +1,202 @@ /* 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) 2015 Minh Ngo */ #include "pythonserver.h" #include PythonServer::PythonServer(QObject* parent) : QObject(parent), m_pModule(nullptr) { } namespace { QString pyObjectToQString(PyObject* obj) { #if PY_MAJOR_VERSION == 3 return QString::fromUtf8(PyUnicode_AsUTF8(obj)); #elif PY_MAJOR_VERSION == 2 return QString::fromLocal8Bit(PyString_AsString(obj)); #else #warning Unknown Python version #endif } } void PythonServer::login() { Py_InspectFlag = 1; Py_Initialize(); m_pModule = PyImport_AddModule("__main__"); filePath = QStringLiteral("python_cantor_worksheet"); } void PythonServer::runPythonCommand(const QString& command) const { PyObject* py_dict = PyModule_GetDict(m_pModule); const char* prepareCommand = "import sys;\n"\ "class CatchOutPythonBackend:\n"\ " def __init__(self):\n"\ " self.value = ''\n"\ " def write(self, txt):\n"\ " self.value += txt\n"\ "outputPythonBackend = CatchOutPythonBackend()\n"\ "errorPythonBackend = CatchOutPythonBackend()\n"\ "sys.stdout = outputPythonBackend\n"\ "sys.stderr = errorPythonBackend\n"; PyRun_SimpleString(prepareCommand); #if PY_MAJOR_VERSION == 3 PyObject* compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input); // There are two reasons for the error: // 1) This code is not single expression, so we can't compile this with flag Py_single_input // 2) There are errors in the code if (PyErr_Occurred()) { PyErr_Clear(); // Try to recompile code as sequence of expressions compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input); if (PyErr_Occurred()) { // We now know, that we have a syntax error, so print the traceback and exit PyErr_PrintEx(0); return; } } PyEval_EvalCode(compile, py_dict, py_dict); #elif PY_MAJOR_VERSION == 2 // Python 2.X don't check, that input string contains only one expression. // So for checking this, we compile string as file and as single expression and compare bytecode // FIXME? PyObject* codeFile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input); if (PyErr_Occurred()) { PyErr_PrintEx(0); return; } PyObject* codeSingle = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input); if (PyErr_Occurred()) { // We have error with Py_single_input, but haven't error with Py_file_input // So, the code can't be compiled as singel input -> use file input right away PyErr_Clear(); PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict); } else { PyObject* bytecode1 = ((PyCodeObject*)codeSingle)->co_code; PyObject* bytecode2 = ((PyCodeObject*)codeFile)->co_code; if (PyObject_Length(bytecode1) >= PyObject_Length(bytecode2)) { PyEval_EvalCode((PyCodeObject*)codeSingle, py_dict, py_dict); } else { PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict); } } #else #warning Unknown Python version #endif if (PyErr_Occurred()) PyErr_PrintEx(0); } QString PythonServer::getError() const { PyObject *errorPython = PyObject_GetAttrString(m_pModule, "errorPythonBackend"); PyObject *error = PyObject_GetAttrString(errorPython, "value"); return pyObjectToQString(error); } QString PythonServer::getOutput() const { PyObject *outputPython = PyObject_GetAttrString(m_pModule, "outputPythonBackend"); PyObject *output = PyObject_GetAttrString(outputPython, "value"); return pyObjectToQString(output); } void PythonServer::setFilePath(const QString& path) { this->filePath = path; PyRun_SimpleString(("__file__ = '"+path.toStdString()+"'").c_str()); } +QString PythonServer::variables() const +{ + // FIXME: This code allows get full form of numpy array, but for big arrays it's could cause performonce problems + // especially for displaying in variables panel + // So, uncomment this, when fix this problem + /* + "try: \n" + " import numpy \n" + " __cantor_numpy_internal__ = numpy.get_printoptions()['threshold'] \n" + " numpy.set_printoptions(threshold=100000000) \n" + "except ModuleNotFoundError: \n" + " pass \n" + + "try: \n" + " import numpy \n" + " numpy.set_printoptions(threshold=__cantor_numpy_internal__) \n" + " del __cantor_numpy_internal__ \n" + "except ModuleNotFoundError: \n" + " pass \n" + */ + + PyRun_SimpleString("__tmp_globals__ = globals()"); + PyObject* globals = PyObject_GetAttrString(m_pModule,"__tmp_globals__"); + PyObject *key, *value; + Py_ssize_t pos = 0; + + QStringList vars; + const QChar sep(30); // INFORMATION SEPARATOR TWO + while (PyDict_Next(globals, &pos, &key, &value)) { + const QString& keyString = pyObjectToQString(key); + if (keyString.startsWith(QLatin1String("__"))) + continue; + + if (keyString == QLatin1String("CatchOutPythonBackend") + || keyString == QLatin1String("errorPythonBackend") + || keyString == QLatin1String("outputPythonBackend")) + continue; + + if (PyModule_Check(value)) + continue; + + if (PyFunction_Check(value)) + continue; + + if (PyType_Check(value)) + continue; + + const QString& valueString = pyObjectToQString(PyObject_Repr(value)); + + vars.append(keyString + QChar(31) + valueString); + } + + return vars.join(sep); +} + diff --git a/src/backends/python/pythonserver.h b/src/backends/python/pythonserver.h index b4cd0f49..b52bc85a 100644 --- a/src/backends/python/pythonserver.h +++ b/src/backends/python/pythonserver.h @@ -1,47 +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) 2015 Minh Ngo */ #ifndef _PYTHONSERVER_H #define _PYTHONSERVER_H #include #include struct _object; using PyObject = _object; class PythonServer : public QObject { Q_OBJECT public: explicit PythonServer(QObject* parent = nullptr); public Q_SLOTS: Q_SCRIPTABLE void login(); Q_SCRIPTABLE void setFilePath(const QString& path); Q_SCRIPTABLE void runPythonCommand(const QString& command) const; Q_SCRIPTABLE QString getOutput() const; Q_SCRIPTABLE QString getError() const; + Q_SCRIPTABLE QString variables() const; private: PyObject* m_pModule; QString filePath; }; #endif diff --git a/src/backends/python/pythonsession.cpp b/src/backends/python/pythonsession.cpp index f0db5e25..f694a7c6 100644 --- a/src/backends/python/pythonsession.cpp +++ b/src/backends/python/pythonsession.cpp @@ -1,407 +1,365 @@ /* 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 "pythonvariablemodel.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_variableModel(new PythonVariableModel(this)) , m_currentExpression(nullptr) , m_pIface(nullptr) , m_pProcess(nullptr) , serverName(serverName) , DbusChannelName(DbusChannelName) , m_pythonVersion(pythonVersion) + , m_needUpdate(false) { } 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_variableModel->setPythonServer(m_pIface); + 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); + m_variableModel->update(); } 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->internalCommand(); readExpressionOutput(command); } // 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() { + m_needUpdate |= !m_currentExpression->isInternal(); if(m_error.isEmpty()){ m_currentExpression->parseOutput(m_output); qDebug() << "output: " << m_output; } else { m_currentExpression->parseError(m_error); qDebug() << "error: " << m_error; } - listVariables(); + if (m_needUpdate) + { + m_variableModel->update(); + m_needUpdate = false; + } 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, 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); + connect ( m_variableModel, &Cantor::DefaultVariableModel::variablesAdded, highlighter, &PythonHighlighter::addUserVariable); + connect ( m_variableModel, &Cantor::DefaultVariableModel::variablesRemoved, highlighter, &PythonHighlighter::removeUserVariable); 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 b2159960..0129ba9e 100644 --- a/src/backends/python/pythonsession.h +++ b/src/backends/python/pythonsession.h @@ -1,106 +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) 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 PythonVariableModel; 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; + PythonVariableModel* m_variableModel; QList m_runningExpressions; PythonExpression* m_currentExpression; QDBusInterface* m_pIface; KProcess* m_pProcess; QString serverName; QString DbusChannelName; QString worksheetPath; int m_pythonVersion; + bool m_needUpdate; + 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 */ diff --git a/src/backends/python/pythonvariablemodel.cpp b/src/backends/python/pythonvariablemodel.cpp new file mode 100644 index 00000000..b3939cd6 --- /dev/null +++ b/src/backends/python/pythonvariablemodel.cpp @@ -0,0 +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) 2018 Nikita Sirgienko +*/ + +#include "pythonvariablemodel.h" +#include "pythonsession.h" +#include "textresult.h" + +#include +#include +#include +#include + +using namespace Cantor; + +PythonVariableModel::PythonVariableModel(PythonSession* session): + DefaultVariableModel(session), + m_pIface(nullptr) +{ +} + +void PythonVariableModel::setPythonServer(QDBusInterface* pIface) +{ + m_pIface = pIface; +} + +void PythonVariableModel::update() +{ + if (!m_pIface) + return; + + const QString& data = QDBusReply(m_pIface->call(QString::fromLatin1("variables"))).value(); + const QStringList& records = data.split(QChar(30), QString::SkipEmptyParts); + + QList variables; + for (const QString& record : records) + { + const QString& name = record.section(QChar(31), 0, 0); + const QString& value = record.section(QChar(31), 1, 1); + + variables << Variable{name, value}; + } + + setVariables(variables); +} diff --git a/src/backends/python/pythonserver.h b/src/backends/python/pythonvariablemodel.h similarity index 56% copy from src/backends/python/pythonserver.h copy to src/backends/python/pythonvariablemodel.h index b4cd0f49..0dab8839 100644 --- a/src/backends/python/pythonserver.h +++ b/src/backends/python/pythonvariablemodel.h @@ -1,47 +1,43 @@ /* 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) 2015 Minh Ngo - */ + Copyright (C) 2019 Nikita Sirgienko +*/ -#ifndef _PYTHONSERVER_H -#define _PYTHONSERVER_H -#include -#include +#ifndef _PYTHONVARIABLEMODEL_H +#define _PYTHONVARIABLEMODEL_H -struct _object; -using PyObject = _object; +#include "defaultvariablemodel.h" -class PythonServer : public QObject +class PythonSession; +class QDBusInterface; + +class PythonVariableModel : public Cantor::DefaultVariableModel { - Q_OBJECT public: - explicit PythonServer(QObject* parent = nullptr); + PythonVariableModel( PythonSession* session); + ~PythonVariableModel() override = default; + + void update() override; - public Q_SLOTS: - Q_SCRIPTABLE void login(); - Q_SCRIPTABLE void setFilePath(const QString& path); - Q_SCRIPTABLE void runPythonCommand(const QString& command) const; - Q_SCRIPTABLE QString getOutput() const; - Q_SCRIPTABLE QString getError() const; + void setPythonServer(QDBusInterface* pIface); private: - PyObject* m_pModule; - QString filePath; + QDBusInterface* m_pIface; }; -#endif +#endif /* _PYTHONVARIABLEMODEL_H */