pythonstylechecking.cpp 7.45 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 * Copyright 2016  Sven Brauch <mail@svenbrauch.de>
 * 
 * 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) version 3 or any later version
 * accepted by the membership of KDE e.V. (or its successor approved
 * by the membership of KDE e.V.), which shall act as a proxy
 * defined in Section 14 of version 3 of the license.
 * 
 * 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, see <http://www.gnu.org/licenses/>.
 * 
 */

#include "pythonstylechecking.h"

#include <QTimer>
#include <QStandardPaths>
26
#include <QRegularExpression>
27 28

#include <language/duchain/problem.h>
29
#include <language/editor/documentrange.h>
30 31 32
#include <interfaces/icore.h>
#include <shell/documentcontroller.h>

33
#include "pythondebug.h"
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
#include "pythonparsejob.h"
#include "helpers.h"

namespace Python {

StyleChecking::StyleChecking(QObject* parent)
    : QObject(parent)
{
    qRegisterMetaType<KDevelop::ReferencedTopDUContext>("KDevelop::ReferencedTopDUContext");
    connect(&m_checkerProcess, &QProcess::readyReadStandardOutput,
            this, &StyleChecking::processOutputStarted);
    connect(&m_checkerProcess, &QProcess::readyReadStandardError,
            [this]() {
                qWarning() << "python code checker error:" << m_checkerProcess.readAllStandardError();
            });
49 50
    auto config = KSharedConfig::openConfig("kdevpythonsupportrc");
    m_pep8Group = config->group("pep8");
51 52 53 54 55 56 57 58 59 60 61
}

StyleChecking::~StyleChecking()
{
    if ( m_checkerProcess.state() == QProcess::Running ) {
        m_checkerProcess.terminate();
        m_checkerProcess.waitForFinished(100);
    }
}

void StyleChecking::startChecker(const QString& text, const QString& select,
62
                                 const QString& ignore, const int maxLineLength)
63 64 65 66 67 68 69
{
    // start up the server
    if ( m_checkerProcess.state() == QProcess::NotRunning ) {
        auto python = Helper::getPythonExecutablePath(nullptr);
        auto serverPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/codestyle.py");
        if ( serverPath.isEmpty() ) {
            qWarning() << "setup problem: codestyle.py not found";
70
            m_mutex.unlock();
71 72 73 74 75 76
            return;
        }
        m_checkerProcess.start(python, {serverPath});
        m_checkerProcess.waitForStarted(30);
        if ( m_checkerProcess.state() != QProcess::Running ) {
            qWarning() << "failed to start code checker process";
77
            m_mutex.unlock();
78 79 80 81 82 83 84 85 86 87 88 89 90 91
            return;
        }
    }

    // send input
    QByteArray data = text.toUtf8();
    QByteArray header;
    header.append(select.toUtf8());
    header.append("\n");
    header.append(ignore.toUtf8());
    header.append("\n");
    header.append(QByteArray::number(maxLineLength));
    header.append("\n");
    // size, always 10 bytes
92
    header.insert(0, QByteArray::number(header.size() + data.size()).leftJustified(10));
93 94 95 96 97 98
    m_checkerProcess.write(header);
    m_checkerProcess.write(data);
}

void StyleChecking::addErrorsToContext(const QVector<QString>& errors)
{
99
    static QRegularExpression errorFormat("(.*):(\\d*):(\\d*): (.*)", QRegularExpression::CaseInsensitiveOption);
100 101 102
    DUChainWriteLocker lock;
    auto document = m_currentlyChecking->url();
    for ( const auto& error : errors ) {
103 104
        QRegularExpressionMatch match;
        if ( (match = errorFormat.match(error)).hasMatch() ) {
105 106
            bool lineno_ok = false;
            bool colno_ok = false;
107 108
            int lineno = match.captured(2).toInt(&lineno_ok);
            int colno = match.captured(3).toInt(&colno_ok);
109
            if ( ! lineno_ok || ! colno_ok ) {
110
                qCDebug(KDEV_PYTHON) << "invalid line / col number";
111 112
                continue;
            }
113
            QString error = match.captured(4);
114
            KDevelop::Problem* p = new KDevelop::Problem();
115 116
            p->setFinalLocation(DocumentRange(document, KTextEditor::Range(lineno - 1, qMax(colno - 1, 0),
                                                                           lineno - 1, colno)));
117 118 119 120 121 122 123
            p->setSource(KDevelop::IProblem::Preprocessor);
            p->setSeverity(error.startsWith('W') ? KDevelop::IProblem::Hint : KDevelop::IProblem::Warning);
            p->setDescription(i18n("PEP8 checker error: %1", error));
            ProblemPointer ptr(p);
            m_currentlyChecking->addProblem(ptr);
        }
        else {
124
            qCDebug(KDEV_PYTHON) << "invalid pep8 error line:" << error;
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
        }
    }

    m_currentlyChecking->setFeatures((TopDUContext::Features) ( m_currentlyChecking->features() | ParseJob::PEP8Checking ));
}

void StyleChecking::processOutputStarted()
{
    // read output size
    QByteArray size_d;
    size_d = m_checkerProcess.read(10);
    bool ok;
    auto size = size_d.toInt(&ok);
    if ( !ok || size < 0 ) {
        addSetupErrorToContext("Got invalid size: " + size_d);
        m_mutex.unlock();
        return;
    }

144
    // read actual output
145 146
    QByteArray buf;
    QTimer t;
147
    t.setSingleShot(true);
148
    t.start(100);
149 150
    while ( size > 0 && t.remainingTime() > 0 ) {
        auto d = m_checkerProcess.read(qMin(4096, size));
151 152
        buf.append(d);
        size -= d.size();
153
        qDebug() << "remaining:" << size << d.size();
154
    }
155

156 157 158 159 160 161
    // process it
    QVector<QString> errors;
    auto ofs = -1;
    auto prev = ofs;
    while ( prev = ofs, (ofs = buf.indexOf('\n', ofs+1)) != -1 ) {
        errors.append(buf.mid(prev+1, ofs-prev));
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
    }
    if ( !t.isActive() ) {
        addSetupErrorToContext("Output took longer than 100 ms.");
    }
    addErrorsToContext(errors);

    // done, unlock mutex
    m_currentlyChecking = nullptr;
    m_mutex.unlock();
}

void StyleChecking::updateStyleChecking(const KDevelop::ReferencedTopDUContext& top)
{
    if ( !top ) {
        return;
    }
    auto url = top->url();
    IDocument* idoc = ICore::self()->documentController()->documentForUrl(url.toUrl());
    if ( !idoc || !idoc->textDocument() || top->features() & ParseJob::PEP8Checking ) {
        return;
    }
    auto text = idoc->textDocument()->text();

    if ( !m_mutex.tryLock(1000) ) {
186
        qWarning() << "timed out waiting for the style checker mutex";
187 188 189
        return;
    }
    m_currentlyChecking = top;
190

191 192
    // default empty is ok, it will never be used, because the config has to be written at least once
    // to even enable this feature.
193 194 195 196
    auto select = m_pep8Group.readEntry<QString>("enableErrors", "");
    auto ignore = m_pep8Group.readEntry<QString>("disableErrors", "");
    auto maxLineLength = m_pep8Group.readEntry<int>("maxLineLength", 80);
    startChecker(text, select, ignore, maxLineLength);
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
}

void StyleChecking::addSetupErrorToContext(const QString& error)
{
    DUChainWriteLocker lock;
    KDevelop::Problem *p = new KDevelop::Problem();
    p->setFinalLocation(DocumentRange(m_currentlyChecking->url(), KTextEditor::Range(0, 0, 0, 0)));
    p->setSource(KDevelop::IProblem::Preprocessor);
    p->setSeverity(KDevelop::IProblem::Warning);
    p->setDescription(i18n("The PEP8 syntax checker does not seem to work correctly.") + "\n" + error);
    ProblemPointer ptr(p);
    m_currentlyChecking->addProblem(ptr);
}

};