diff --git a/codestyle.py b/codestyle.py index 843f8c18..ad995046 100755 --- a/codestyle.py +++ b/codestyle.py @@ -1,48 +1,50 @@ #!/usr/bin/env python3 from contextlib import redirect_stdout from io import StringIO from sys import stdin, stdout, stderr import sys try: from pycodestyle import Checker, StyleGuide except ImportError: try: from pep8 import Checker, StyleGuide except ImportError: Checker = None while True: size = stdin.buffer.read(10) size = int(size) if not size > 0: continue buf = bytes() while len(buf) < size: buf += stdin.buffer.read(min(1024, size - len(buf))) lines = buf.decode("utf-8").splitlines() opts, text = lines[:3], [l + "\n" for l in lines[3:]] if Checker is not None: style_guide = StyleGuide() options = style_guide.options select = [x for x in opts[0].strip().split(',') if len(x) > 0] ignore = [x for x in opts[1].strip().split(',') if len(x) > 0] options.select = tuple(select) options.ignore = tuple(ignore) options.max_line_length = int(opts[2]) stderr.flush() c = Checker(lines=text, options=options) output = StringIO() with redirect_stdout(output): # writes directly to stdout, so catch it ... c.check_all() output = output.getvalue() output = output[:2**15] stdout.write("{0:>10}".format(len(output))) stdout.write(output) stdout.flush() else: stderr.write("The `pycodestyle` (previously `pep8`) module is not installed.") stderr.flush() + stdout.write("{0:>10}".format(0)) + stdout.flush() diff --git a/pythonstylechecking.cpp b/pythonstylechecking.cpp index fae78388..0701286c 100644 --- a/pythonstylechecking.cpp +++ b/pythonstylechecking.cpp @@ -1,209 +1,211 @@ /* * Copyright 2016 Sven Brauch * * 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 . * */ #include "pythonstylechecking.h" #include #include #include #include #include #include #include #include "pythondebug.h" #include "pythonparsejob.h" #include "helpers.h" namespace Python { StyleChecking::StyleChecking(QObject* parent) : QObject(parent) { qRegisterMetaType("KDevelop::ReferencedTopDUContext"); connect(&m_checkerProcess, &QProcess::readyReadStandardOutput, this, &StyleChecking::processOutputStarted); connect(&m_checkerProcess, &QProcess::readyReadStandardError, [this]() { qWarning() << "python code checker error:" << m_checkerProcess.readAllStandardError(); }); auto config = KSharedConfig::openConfig("kdevpythonsupportrc"); m_pep8Group = config->group("pep8"); } StyleChecking::~StyleChecking() { if ( m_checkerProcess.state() == QProcess::Running ) { m_checkerProcess.terminate(); m_checkerProcess.waitForFinished(100); } } void StyleChecking::startChecker(const QString& text, const QString& select, const QString& ignore, const int maxLineLength) { // 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"; + m_mutex.unlock(); return; } m_checkerProcess.start(python, {serverPath}); m_checkerProcess.waitForStarted(30); if ( m_checkerProcess.state() != QProcess::Running ) { qWarning() << "failed to start code checker process"; + m_mutex.unlock(); 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 header.insert(0, QByteArray::number(header.size() + data.size()).leftJustified(10)); m_checkerProcess.write(header); m_checkerProcess.write(data); } void StyleChecking::addErrorsToContext(const QVector& errors) { static QRegularExpression errorFormat("(.*):(\\d*):(\\d*): (.*)", QRegularExpression::CaseInsensitiveOption); DUChainWriteLocker lock; auto document = m_currentlyChecking->url(); for ( const auto& error : errors ) { QRegularExpressionMatch match; if ( (match = errorFormat.match(error)).hasMatch() ) { bool lineno_ok = false; bool colno_ok = false; int lineno = match.captured(2).toInt(&lineno_ok); int colno = match.captured(3).toInt(&colno_ok); if ( ! lineno_ok || ! colno_ok ) { qCDebug(KDEV_PYTHON) << "invalid line / col number"; continue; } QString error = match.captured(4); KDevelop::Problem* p = new KDevelop::Problem(); p->setFinalLocation(DocumentRange(document, KTextEditor::Range(lineno - 1, qMax(colno - 1, 0), lineno - 1, colno))); 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 { qCDebug(KDEV_PYTHON) << "invalid pep8 error line:" << error; } } 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; } // read actual output QByteArray buf; QTimer t; t.setSingleShot(true); t.start(100); while ( size > 0 && t.remainingTime() > 0 ) { auto d = m_checkerProcess.read(qMin(4096, size)); buf.append(d); size -= d.size(); qDebug() << "remaining:" << size << d.size(); } // process it QVector 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)); } 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) ) { qWarning() << "timed out waiting for the style checker mutex"; return; } m_currentlyChecking = top; // default empty is ok, it will never be used, because the config has to be written at least once // to even enable this feature. auto select = m_pep8Group.readEntry("enableErrors", ""); auto ignore = m_pep8Group.readEntry("disableErrors", ""); auto maxLineLength = m_pep8Group.readEntry("maxLineLength", 80); startChecker(text, select, ignore, maxLineLength); } 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); } };