diff --git a/share/qtcreator/templates/wizards/classes/python/file.py b/share/qtcreator/templates/wizards/classes/python/file.py index 32be237c7a1..adcbc1ed2d0 100644 --- a/share/qtcreator/templates/wizards/classes/python/file.py +++ b/share/qtcreator/templates/wizards/classes/python/file.py @@ -1,30 +1,26 @@ -# -*- coding: utf-8 -*- - -@if '%{Imports}' -try: -@if '%{ImportQtCore}' - from PySide import QtCore -@endif -@if '%{ImportQtWidgets}' - from PySide import QtWidgets -@endif -@if '%{ImportQtQuick}' - from PySide import QtQuick -@endif -except: -@if '%{ImportQtCore}' - from PyQt5.QtCore import pyqtSlot as Slot - from PyQt5 import QtCore -@endif -@if '%{ImportQtWidgets}' - from PyQt5 import QtWidgets -@endif -@if '%{ImportQtQuick}' - from PyQt5 import QtQuick +# This Python file uses the following encoding: utf-8 +@if '%{Module}' === 'PySide2' + @if '%{ImportQtCore}' +from PySide2 import QtCore + @endif + @if '%{ImportQtWidgets}' +from PySide2 import QtWidgets + @endif + @if '%{ImportQtQuick}' +from PySide2 import QtQuick + @endif +@else + @if '%{ImportQtCore}' +from PyQt5 import QtCore + @endif + @if '%{ImportQtWidgets}' +from PyQt5 import QtWidgets + @endif + @if '%{ImportQtQuick}' +from PyQt5 import QtQuick + @endif @endif - -@endif @if '%{Base}' class %{Class}(%{Base}): @else diff --git a/share/qtcreator/templates/wizards/classes/python/wizard.json b/share/qtcreator/templates/wizards/classes/python/wizard.json index 64c8d8d435a..6d1d72c63bb 100644 --- a/share/qtcreator/templates/wizards/classes/python/wizard.json +++ b/share/qtcreator/templates/wizards/classes/python/wizard.json @@ -6,7 +6,7 @@ "trDescription": "Creates new Python class file.", "trDisplayName": "Python Class", "trDisplayCategory": "Python", - "iconText": "py", + "icon": "../../files/python/icon.png", "enabled": "%{JS: [ %{Plugins} ].indexOf('PythonEditor') >= 0}", "options": @@ -30,6 +30,15 @@ "type": "LineEdit", "data": { "validator": "^(?:[^\\d\\W]\\w*|)$" } }, + { + "name": "Module", + "trDisplayName": "Python module:", + "type": "ComboBox", + "data": + { + "items": ["PySide2", "PyQt5"] + } + }, { "name": "BaseCB", "trDisplayName": "Base class:", diff --git a/share/qtcreator/templates/wizards/files/python/file.py b/share/qtcreator/templates/wizards/files/python/file.py index faa18be5bbf..003f8414973 100644 --- a/share/qtcreator/templates/wizards/files/python/file.py +++ b/share/qtcreator/templates/wizards/files/python/file.py @@ -1,2 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +# This Python file uses the following encoding: utf-8 + +# if__name__ == "__main__": +# pass diff --git a/share/qtcreator/templates/wizards/files/python/icon.png b/share/qtcreator/templates/wizards/files/python/icon.png new file mode 100644 index 00000000000..8ab694fe7af Binary files /dev/null and b/share/qtcreator/templates/wizards/files/python/icon.png differ diff --git a/share/qtcreator/templates/wizards/files/python/icon@2x.png b/share/qtcreator/templates/wizards/files/python/icon@2x.png new file mode 100644 index 00000000000..8d1f2751860 Binary files /dev/null and b/share/qtcreator/templates/wizards/files/python/icon@2x.png differ diff --git a/share/qtcreator/templates/wizards/files/python/wizard.json b/share/qtcreator/templates/wizards/files/python/wizard.json index 32b720f3d40..d4a6cfd0126 100644 --- a/share/qtcreator/templates/wizards/files/python/wizard.json +++ b/share/qtcreator/templates/wizards/files/python/wizard.json @@ -6,7 +6,7 @@ "trDescription": "Creates an empty Python script file using UTF-8 charset.", "trDisplayName": "Python File", "trDisplayCategory": "Python", - "iconText": "py", + "icon": "icon.png", "enabled": "%{JS: [ %{Plugins} ].indexOf('PythonEditor') >= 0}", "pages" : diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/icon.png b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/icon.png new file mode 100644 index 00000000000..483f71196d3 Binary files /dev/null and b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/icon.png differ diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/icon@2x.png b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/icon@2x.png new file mode 100644 index 00000000000..81beed5e239 Binary files /dev/null and b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/icon@2x.png differ diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json new file mode 100644 index 00000000000..0f1ce91cf78 --- /dev/null +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "supportedProjectTypes": [ "PythonProject" ], + "id": "U.QtForPythonApplicationEmpty", + "category": "F.Application", + "trDescription": "Creates a Qt for Python application that only the main code for a QApplication", + "trDisplayName": "Qt for Python - Empty", + "trDisplayCategory": "Application", + "icon": "icon.png", + "enabled": "%{JS: [ %{Plugins} ].indexOf('PythonEditor') >= 0}", + "featuresRequired": [ "QtSupport.Wizards.FeatureQt.5.6" ], + + "options": + [ + { "key": "MainPyFileName", "value": "main.py" }, + { "key": "PyProjectFile", "value": "main.pyproject" } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project" + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../main.pyproject", + "target": "%{PyProjectFile}", + "openAsProject": true + }, + { + "source": "../main_empty.py", + "target": "%{MainPyFileName}", + "openInEditor": true + } + ] + } + ] +} diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/main.pyproject b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/main.pyproject new file mode 100644 index 00000000000..cc7a74a3468 --- /dev/null +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/main.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py"] +} diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/main_empty.py b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/main_empty.py new file mode 100644 index 00000000000..6cb021403b9 --- /dev/null +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/main_empty.py @@ -0,0 +1,9 @@ +# This Python file uses the following encoding: utf-8 +import sys +from PySide2.QtWidgets import QApplication + + +if __name__ == "__main__": + app = QApplication([]) + # ... + sys.exit(app.exec_()) diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/main_mainwindow.py b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/main_mainwindow.py new file mode 100644 index 00000000000..fa1f92c7477 --- /dev/null +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/main_mainwindow.py @@ -0,0 +1,15 @@ +# This Python file uses the following encoding: utf-8 +import sys +from PySide2.QtWidgets import QApplication, QMainWindow + + +class MainWindow(QMainWindow): + def __init__(self): + QMainWindow.__init__(self) + + +if __name__ == "__main__": + app = QApplication([]) + window = MainWindow() + window.show() + sys.exit(app.exec_()) diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/icon.png b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/icon.png new file mode 100644 index 00000000000..ba97ffd6620 Binary files /dev/null and b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/icon.png differ diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/icon@2x.png b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/icon@2x.png new file mode 100644 index 00000000000..4c26be65ba4 Binary files /dev/null and b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/icon@2x.png differ diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json new file mode 100644 index 00000000000..c98ebb3ed46 --- /dev/null +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "supportedProjectTypes": [ "Qt4ProjectManager.Qt4Project" ], + "id": "U.QtForPythonApplicationWindow", + "category": "F.Application", + "trDescription": "Creates a Qt for Python application that contains an empty window.", + "trDisplayName": "Qt for Python - Window", + "trDisplayCategory": "Application", + "icon": "icon.png", + "enabled": "%{JS: [ %{Plugins} ].indexOf('PythonEditor') >= 0}", + "featuresRequired": [ "QtSupport.Wizards.FeatureQt.5.6" ], + + "options": + [ + { "key": "MainPyFileName", "value": "main.py" }, + { "key": "PyProjectFile", "value": "main.pyproject" } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project" + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../main.pyproject", + "target": "%{PyProjectFile}", + "openAsProject": true + }, + { + "source": "../main_mainwindow.py", + "target": "%{MainPyFileName}", + "openInEditor": true + } + ] + } + ] +} diff --git a/src/libs/languageserverprotocol/basemessage.cpp b/src/libs/languageserverprotocol/basemessage.cpp index 65fc12e4499..1ee17e3eee3 100644 --- a/src/libs/languageserverprotocol/basemessage.cpp +++ b/src/libs/languageserverprotocol/basemessage.cpp @@ -176,7 +176,7 @@ bool BaseMessage::isValid() const return contentLength >= 0; } -QByteArray BaseMessage::toData() +QByteArray BaseMessage::toData() const { return header() + content; } diff --git a/src/libs/languageserverprotocol/basemessage.h b/src/libs/languageserverprotocol/basemessage.h index 9c35ece1b0e..ba8238291ad 100644 --- a/src/libs/languageserverprotocol/basemessage.h +++ b/src/libs/languageserverprotocol/basemessage.h @@ -55,7 +55,7 @@ public: bool isComplete() const; bool isValid() const; - QByteArray toData(); + QByteArray toData() const; QByteArray mimeType; QByteArray content; diff --git a/src/plugins/autotest/qtest/qttestoutputreader.cpp b/src/plugins/autotest/qtest/qttestoutputreader.cpp index 06675dade58..97fb6dc3cf0 100644 --- a/src/plugins/autotest/qtest/qttestoutputreader.cpp +++ b/src/plugins/autotest/qtest/qttestoutputreader.cpp @@ -32,7 +32,6 @@ #include #include -#include #include namespace Autotest { @@ -41,17 +40,16 @@ namespace Internal { static QString decode(const QString& original) { QString result(original); - static QRegExp regex("&#((x[0-9A-F]+)|([0-9]+));", Qt::CaseInsensitive); - regex.setMinimal(true); + static const QRegularExpression regex("&#((x[[:xdigit:]]+)|(\\d+));"); - int pos = 0; - while ((pos = regex.indexIn(original, pos)) != -1) { - const QString value = regex.cap(1); + QRegularExpressionMatchIterator it = regex.globalMatch(original); + while (it.hasNext()) { + const QRegularExpressionMatch match = it.next(); + const QString value = match.captured(1); if (value.startsWith('x')) - result.replace(regex.cap(0), QChar(value.midRef(1).toInt(nullptr, 16))); + result.replace(match.captured(0), QChar(value.midRef(1).toInt(nullptr, 16))); else - result.replace(regex.cap(0), QChar(value.toInt(nullptr, 10))); - pos += regex.matchedLength(); + result.replace(match.captured(0), QChar(value.toInt(nullptr, 10))); } return result; @@ -347,39 +345,48 @@ static QStringList extractFunctionInformation(const QString &testClassName, void QtTestOutputReader::processPlainTextOutput(const QByteArray &outputLineWithNewLine) { - static QRegExp start("^[*]{9} Start testing of (.*) [*]{9}$"); - static QRegExp config("^Config: Using QtTest library (.*), (Qt (\\d+(\\.\\d+){2}) \\(.*\\))$"); - static QRegExp summary("^Totals: \\d+ passed, \\d+ failed, \\d+ skipped(, \\d+ blacklisted)?$"); - static QRegExp finish("^[*]{9} Finished testing of (.*) [*]{9}$"); + static const QRegularExpression start("^[*]{9} Start testing of (.*) [*]{9}$"); + static const QRegularExpression config("^Config: Using QtTest library (.*), " + "(Qt (\\d+(\\.\\d+){2}) \\(.*\\))$"); + static const QRegularExpression summary("^Totals: \\d+ passed, \\d+ failed, " + "\\d+ skipped(, \\d+ blacklisted)?$"); + static const QRegularExpression finish("^[*]{9} Finished testing of (.*) [*]{9}$"); - static QRegExp result("^(PASS |FAIL! |XFAIL |XPASS |SKIP |RESULT " - "|BPASS |BFAIL |BXPASS |BXFAIL " - "|INFO |QWARN |WARNING|QDEBUG |QSYSTEM): (.*)$"); + static const QRegularExpression result("^(PASS |FAIL! |XFAIL |XPASS |SKIP |RESULT " + "|BPASS |BFAIL |BXPASS |BXFAIL " + "|INFO |QWARN |WARNING|QDEBUG |QSYSTEM): (.*)$"); - static QRegExp benchDetails("^\\s+([\\d,.]+ .* per iteration \\(total: [\\d,.]+, iterations: \\d+\\))$"); - static QRegExp locationUnix(QT_TEST_FAIL_UNIX_REGEXP); - static QRegExp locationWin(QT_TEST_FAIL_WIN_REGEXP); + static const QRegularExpression benchDetails("^\\s+([\\d,.]+ .* per iteration " + "\\(total: [\\d,.]+, iterations: \\d+\\))$"); + static const QRegularExpression locationUnix(QT_TEST_FAIL_UNIX_REGEXP); + static const QRegularExpression locationWin(QT_TEST_FAIL_WIN_REGEXP); if (m_futureInterface.isCanceled()) return; const QString line = QString::fromUtf8(chopLineBreak(outputLineWithNewLine)); + QRegularExpressionMatch match; - if (result.exactMatch(line)) { - processResultOutput(result.cap(1).toLower().trimmed(), result.cap(2)); - } else if (locationUnix.exactMatch(line)) { - processLocationOutput(locationUnix.cap(1)); - } else if (locationWin.exactMatch(line)) { - processLocationOutput(locationWin.cap(1)); - } else if (benchDetails.exactMatch(line)) { - m_description = benchDetails.cap(1); - } else if (config.exactMatch(line)) { - handleAndSendConfigMessage(config); - } else if (start.exactMatch(line)) { - m_className = start.cap(1); + auto hasMatch = [&match, line](const QRegularExpression ®ex) { + match = regex.match(line); + return match.hasMatch(); + }; + + if (hasMatch(result)) { + processResultOutput(match.captured(1).toLower().trimmed(), match.captured(2)); + } else if (hasMatch(locationUnix)) { + processLocationOutput(match.captured(1)); + } else if (hasMatch(locationWin)) { + processLocationOutput(match.captured(1)); + } else if (hasMatch(benchDetails)) { + m_description = match.captured(1); + } else if (hasMatch(config)) { + handleAndSendConfigMessage(match); + } else if (hasMatch(start)) { + m_className = match.captured(1); QTC_CHECK(!m_className.isEmpty()); sendStartMessage(false); - } else if (summary.exactMatch(line) || finish.exactMatch(line)) { + } else if (summary.match(line).hasMatch() || finish.match(line).hasMatch()) { processSummaryFinishOutput(); } else { // we have some plain output, but we cannot say where for sure it belongs to.. if (!m_description.isEmpty()) @@ -507,19 +514,19 @@ void QtTestOutputReader::sendFinishMessage(bool isFunction) reportResult(testResult); } -void QtTestOutputReader::handleAndSendConfigMessage(const QRegExp &config) +void QtTestOutputReader::handleAndSendConfigMessage(const QRegularExpressionMatch &config) { TestResultPtr testResult = createDefaultResult(); testResult->setResult(Result::MessageInternal); - testResult->setDescription(trQtVersion(config.cap(3))); + testResult->setDescription(trQtVersion(config.captured(3))); reportResult(testResult); testResult = createDefaultResult(); testResult->setResult(Result::MessageInternal); - testResult->setDescription(trQtBuild(config.cap(2))); + testResult->setDescription(trQtBuild(config.captured(2))); reportResult(testResult); testResult = createDefaultResult(); testResult->setResult(Result::MessageInternal); - testResult->setDescription(trQtestVersion(config.cap(1))); + testResult->setDescription(trQtestVersion(config.captured(1))); reportResult(testResult); } diff --git a/src/plugins/autotest/qtest/qttestoutputreader.h b/src/plugins/autotest/qtest/qttestoutputreader.h index 89da00a8b91..a95a51a662f 100644 --- a/src/plugins/autotest/qtest/qttestoutputreader.h +++ b/src/plugins/autotest/qtest/qttestoutputreader.h @@ -65,7 +65,7 @@ private: void sendMessageCurrentTest(); void sendStartMessage(bool isFunction); void sendFinishMessage(bool isFunction); - void handleAndSendConfigMessage(const QRegExp &config); + void handleAndSendConfigMessage(const QRegularExpressionMatch &config); enum CDATAMode { diff --git a/src/plugins/languageclient/baseclient.cpp b/src/plugins/languageclient/client.cpp similarity index 82% rename from src/plugins/languageclient/baseclient.cpp rename to src/plugins/languageclient/client.cpp index 02f2448521d..b208f8ab156 100644 --- a/src/plugins/languageclient/baseclient.cpp +++ b/src/plugins/languageclient/client.cpp @@ -23,10 +23,11 @@ ** ****************************************************************************/ -#include "baseclient.h" +#include "client.h" +#include "languageclientinterface.h" #include "languageclientmanager.h" -#include "languageclient/languageclientutils.h" +#include "languageclientutils.h" #include #include @@ -60,22 +61,23 @@ using namespace Utils; namespace LanguageClient { static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client", QtWarningMsg); -static Q_LOGGING_CATEGORY(LOGLSPCLIENTV, "qtc.languageclient.messages", QtWarningMsg); -static Q_LOGGING_CATEGORY(LOGLSPCLIENTPARSE, "qtc.languageclient.parse", QtWarningMsg); -BaseClient::BaseClient() +Client::Client(BaseClientInterface *clientInterface) : m_id(Core::Id::fromString(QUuid::createUuid().toString())) , m_completionProvider(this) + , m_clientInterface(clientInterface) { - m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); m_contentHandler.insert(JsonRpcMessageHandler::jsonRpcMimeType(), &JsonRpcMessageHandler::parseContent); + QTC_ASSERT(clientInterface, return); + connect(clientInterface, &BaseClientInterface::messageReceived, this, &Client::handleMessage); + connect(clientInterface, &BaseClientInterface::error, this, &Client::setError); + connect(clientInterface, &BaseClientInterface::finished, this, &Client::finished); } -BaseClient::~BaseClient() +Client::~Client() { using namespace TextEditor; - m_buffer.close(); // FIXME: instead of replacing the completion provider in the text document store the // completion provider as a prioritised list in the text document for (TextDocument *document : m_resetCompletionProvider) @@ -88,9 +90,10 @@ BaseClient::~BaseClient() } } -void BaseClient::initialize() +void Client::initialize() { using namespace ProjectExplorer; + QTC_ASSERT(m_clientInterface, return); QTC_ASSERT(m_state == Uninitialized, return); qCDebug(LOGLSPCLIENT) << "initializing language server " << m_displayName; auto initRequest = new InitializeRequest(); @@ -108,11 +111,11 @@ void BaseClient::initialize() }); // directly send data otherwise the state check would fail; initRequest->registerResponseHandler(&m_responseHandlers); - sendData(initRequest->toBaseMessage().toData()); + m_clientInterface->sendMessage(initRequest->toBaseMessage()); m_state = InitializeRequested; } -void BaseClient::shutdown() +void Client::shutdown() { QTC_ASSERT(m_state == Initialized, emit finished(); return); qCDebug(LOGLSPCLIENT) << "shutdown language server " << m_displayName; @@ -124,12 +127,12 @@ void BaseClient::shutdown() m_state = ShutdownRequested; } -BaseClient::State BaseClient::state() const +Client::State Client::state() const { return m_state; } -void BaseClient::openDocument(Core::IDocument *document) +void Client::openDocument(Core::IDocument *document) { using namespace TextEditor; if (!isSupportedDocument(document)) @@ -188,35 +191,36 @@ void BaseClient::openDocument(Core::IDocument *document) requestDocumentSymbols(textDocument); } -void BaseClient::sendContent(const IContent &content) +void Client::sendContent(const IContent &content) { + QTC_ASSERT(m_clientInterface, return); QTC_ASSERT(m_state == Initialized, return); content.registerResponseHandler(&m_responseHandlers); QString error; if (!QTC_GUARD(content.isValid(&error))) Core::MessageManager::write(error); - sendData(content.toBaseMessage().toData()); + m_clientInterface->sendMessage(content.toBaseMessage()); } -void BaseClient::sendContent(const DocumentUri &uri, const IContent &content) +void Client::sendContent(const DocumentUri &uri, const IContent &content) { if (!m_openedDocument.contains(uri.toFileName())) return; sendContent(content); } -void BaseClient::cancelRequest(const MessageId &id) +void Client::cancelRequest(const MessageId &id) { m_responseHandlers.remove(id); sendContent(CancelRequest(CancelParameter(id))); } -void BaseClient::closeDocument(const DidCloseTextDocumentParams ¶ms) +void Client::closeDocument(const DidCloseTextDocumentParams ¶ms) { sendContent(params.textDocument().uri(), DidCloseTextDocumentNotification(params)); } -void BaseClient::documentContentsSaved(Core::IDocument *document) +void Client::documentContentsSaved(Core::IDocument *document) { if (!m_openedDocument.contains(document->filePath())) return; @@ -250,7 +254,7 @@ void BaseClient::documentContentsSaved(Core::IDocument *document) sendContent(DidSaveTextDocumentNotification(params)); } -void BaseClient::documentWillSave(Core::IDocument *document) +void Client::documentWillSave(Core::IDocument *document) { const FileName &filePath = document->filePath(); if (!m_openedDocument.contains(filePath)) @@ -278,7 +282,7 @@ void BaseClient::documentWillSave(Core::IDocument *document) sendContent(WillSaveTextDocumentNotification(params)); } -void BaseClient::documentContentsChanged(Core::IDocument *document) +void Client::documentContentsChanged(Core::IDocument *document) { if (!m_openedDocument.contains(document->filePath())) return; @@ -311,18 +315,18 @@ void BaseClient::documentContentsChanged(Core::IDocument *document) } } -void BaseClient::registerCapabilities(const QList ®istrations) +void Client::registerCapabilities(const QList ®istrations) { m_dynamicCapabilities.registerCapability(registrations); } -void BaseClient::unregisterCapabilities(const QList &unregistrations) +void Client::unregisterCapabilities(const QList &unregistrations) { m_dynamicCapabilities.unregisterCapability(unregistrations); } template -static bool sendTextDocumentPositionParamsRequest(BaseClient *client, +static bool sendTextDocumentPositionParamsRequest(Client *client, const Request &request, const DynamicCapabilities &dynamicCapabilities, const optional &serverCapability) @@ -346,13 +350,13 @@ static bool sendTextDocumentPositionParamsRequest(BaseClient *client, return sendMessage; } -bool BaseClient::findLinkAt(GotoDefinitionRequest &request) +bool Client::findLinkAt(GotoDefinitionRequest &request) { return LanguageClient::sendTextDocumentPositionParamsRequest( this, request, m_dynamicCapabilities, m_serverCapabilities.definitionProvider()); } -bool BaseClient::findUsages(FindReferencesRequest &request) +bool Client::findUsages(FindReferencesRequest &request) { return LanguageClient::sendTextDocumentPositionParamsRequest( this, request, m_dynamicCapabilities, m_serverCapabilities.referencesProvider()); @@ -367,7 +371,7 @@ TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation info.name().length(), info.kind()); } -void BaseClient::requestDocumentSymbols(TextEditor::TextDocument *document) +void Client::requestDocumentSymbols(TextEditor::TextDocument *document) { // TODO: Do not use this information for highlighting but the overview model return; @@ -460,7 +464,7 @@ void BaseClient::requestDocumentSymbols(TextEditor::TextDocument *document) sendContent(request); } -void BaseClient::cursorPositionChanged(TextEditor::TextEditorWidget *widget) +void Client::cursorPositionChanged(TextEditor::TextEditorWidget *widget) { const auto uri = DocumentUri::fromFileName(widget->textDocument()->filePath()); if (m_dynamicCapabilities.isRegistered(DocumentHighlightsRequest::methodName).value_or(false)) { @@ -511,7 +515,7 @@ void BaseClient::cursorPositionChanged(TextEditor::TextEditorWidget *widget) sendContent(request); } -void BaseClient::requestCodeActions(const DocumentUri &uri, const QList &diagnostics) +void Client::requestCodeActions(const DocumentUri &uri, const QList &diagnostics) { const Utils::FileName fileName = uri.toFileName(); TextEditor::TextDocument *doc = textDocumentForFileName(fileName); @@ -544,14 +548,14 @@ void BaseClient::requestCodeActions(const DocumentUri &uri, const QList(this)](const CodeActionRequest::Response &response) { + [uri, self = QPointer(this)](const CodeActionRequest::Response &response) { if (self) self->handleCodeActionResponse(response, uri); }); sendContent(request); } -void BaseClient::handleCodeActionResponse(const CodeActionRequest::Response &response, +void Client::handleCodeActionResponse(const CodeActionRequest::Response &response, const DocumentUri &uri) { if (const Utils::optional &error = response.error()) @@ -570,7 +574,7 @@ void BaseClient::handleCodeActionResponse(const CodeActionRequest::Response &res } } -void BaseClient::executeCommand(const Command &command) +void Client::executeCommand(const Command &command) { using CommandOptions = LanguageServerProtocol::ServerCapabilities::ExecuteCommandOptions; const QString method(ExecuteCommandRequest::methodName); @@ -591,7 +595,7 @@ void BaseClient::executeCommand(const Command &command) sendContent(request); } -void BaseClient::projectOpened(ProjectExplorer::Project *project) +void Client::projectOpened(ProjectExplorer::Project *project) { if (!sendWorkspceFolderChanges()) return; @@ -603,7 +607,7 @@ void BaseClient::projectOpened(ProjectExplorer::Project *project) sendContent(change); } -void BaseClient::projectClosed(ProjectExplorer::Project *project) +void Client::projectClosed(ProjectExplorer::Project *project) { if (!sendWorkspceFolderChanges()) return; @@ -615,18 +619,18 @@ void BaseClient::projectClosed(ProjectExplorer::Project *project) sendContent(change); } -void BaseClient::setSupportedLanguage(const LanguageFilter &filter) +void Client::setSupportedLanguage(const LanguageFilter &filter) { m_languagFilter = filter; } -bool BaseClient::isSupportedDocument(const Core::IDocument *document) const +bool Client::isSupportedDocument(const Core::IDocument *document) const { QTC_ASSERT(document, return false); return isSupportedFile(document->filePath(), document->mimeType()); } -bool BaseClient::isSupportedFile(const Utils::FileName &filePath, const QString &mimeType) const +bool Client::isSupportedFile(const Utils::FileName &filePath, const QString &mimeType) const { if (m_languagFilter.mimeTypes.isEmpty() && m_languagFilter.filePattern.isEmpty()) return true; @@ -640,63 +644,84 @@ bool BaseClient::isSupportedFile(const Utils::FileName &filePath, const QString }); } -bool BaseClient::isSupportedUri(const DocumentUri &uri) const +bool Client::isSupportedUri(const DocumentUri &uri) const { return isSupportedFile(uri.toFileName(), Utils::mimeTypeForFile(uri.toFileName().fileName()).name()); } -bool BaseClient::needsRestart(const BaseSettings *settings) const +bool Client::needsRestart(const BaseSettings *settings) const { QTC_ASSERT(settings, return false); return m_languagFilter.mimeTypes != settings->m_languageFilter.mimeTypes || m_languagFilter.filePattern != settings->m_languageFilter.filePattern; } -bool BaseClient::reset() +bool Client::start() +{ + return m_clientInterface->start(); +} + +bool Client::reset() { if (!m_restartsLeft) return false; --m_restartsLeft; m_state = Uninitialized; m_responseHandlers.clear(); - m_buffer.close(); - m_buffer.setData(nullptr); - m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); + m_clientInterface->resetBuffer(); m_openedDocument.clear(); m_serverCapabilities = ServerCapabilities(); m_dynamicCapabilities.reset(); return true; } -void BaseClient::setError(const QString &message) +void Client::setError(const QString &message) { log(message); m_state = Error; } -void BaseClient::log(const QString &message, Core::MessageManager::PrintToOutputPaneFlag flag) +void Client::handleMessage(const BaseMessage &message) +{ + if (auto handler = m_contentHandler[message.mimeType]) { + QString parseError; + handler(message.content, message.codec, parseError, + [this](MessageId id, const QByteArray &content, QTextCodec *codec){ + this->handleResponse(id, content, codec); + }, + [this](const QString &method, MessageId id, const IContent *content){ + this->handleMethod(method, id, content); + }); + if (!parseError.isEmpty()) + log(parseError); + } else { + log(tr("Cannot handle content of type: %1").arg(QLatin1String(message.mimeType))); + } +} + +void Client::log(const QString &message, Core::MessageManager::PrintToOutputPaneFlag flag) { Core::MessageManager::write(QString("LanguageClient %1: %2").arg(name(), message), flag); } -const ServerCapabilities &BaseClient::capabilities() const +const ServerCapabilities &Client::capabilities() const { return m_serverCapabilities; } -const DynamicCapabilities &BaseClient::dynamicCapabilities() const +const DynamicCapabilities &Client::dynamicCapabilities() const { return m_dynamicCapabilities; } -void BaseClient::log(const ShowMessageParams &message, +void Client::log(const ShowMessageParams &message, Core::MessageManager::PrintToOutputPaneFlag flag) { log(message.toString(), flag); } -void BaseClient::showMessageBox(const ShowMessageRequestParams &message, const MessageId &id) +void Client::showMessageBox(const ShowMessageRequestParams &message, const MessageId &id) { auto box = new QMessageBox(); box->setText(message.toString()); @@ -724,13 +749,13 @@ void BaseClient::showMessageBox(const ShowMessageRequestParams &message, const M box->show(); } -void BaseClient::handleResponse(const MessageId &id, const QByteArray &content, QTextCodec *codec) +void Client::handleResponse(const MessageId &id, const QByteArray &content, QTextCodec *codec) { if (auto handler = m_responseHandlers[id]) handler(content, codec); } -void BaseClient::handleMethod(const QString &method, MessageId id, const IContent *content) +void Client::handleMethod(const QString &method, MessageId id, const IContent *content) { QStringList error; bool paramsValid = true; @@ -798,7 +823,7 @@ void BaseClient::handleMethod(const QString &method, MessageId id, const IConten delete content; } -void BaseClient::intializeCallback(const InitializeRequest::Response &initResponse) +void Client::intializeCallback(const InitializeRequest::Response &initResponse) { QTC_ASSERT(m_state == InitializeRequested, return); if (optional> error = initResponse.error()) { @@ -839,9 +864,10 @@ void BaseClient::intializeCallback(const InitializeRequest::Response &initRespon openDocument(openedDocument); } -void BaseClient::shutDownCallback(const ShutdownRequest::Response &shutdownResponse) +void Client::shutDownCallback(const ShutdownRequest::Response &shutdownResponse) { QTC_ASSERT(m_state == ShutdownRequested, return); + QTC_ASSERT(m_clientInterface, return); optional errorValue = shutdownResponse.error(); if (errorValue.has_value()) { ShutdownRequest::Response::Error error = errorValue.value(); @@ -849,12 +875,12 @@ void BaseClient::shutDownCallback(const ShutdownRequest::Response &shutdownRespo return; } // directly send data otherwise the state check would fail; - sendData(ExitNotification().toBaseMessage().toData()); + m_clientInterface->sendMessage(ExitNotification().toBaseMessage()); qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " shutdown"; m_state = Shutdown; } -bool BaseClient::sendWorkspceFolderChanges() const +bool Client::sendWorkspceFolderChanges() const { if (m_dynamicCapabilities.isRegistered( DidChangeWorkspaceFoldersNotification::methodName).value_or(false)) { @@ -873,118 +899,4 @@ bool BaseClient::sendWorkspceFolderChanges() const return false; } -void BaseClient::parseData(const QByteArray &data) -{ - const qint64 preWritePosition = m_buffer.pos(); - qCDebug(LOGLSPCLIENTPARSE) << "parse buffer pos: " << preWritePosition; - qCDebug(LOGLSPCLIENTPARSE) << " data: " << data; - if (!m_buffer.atEnd()) - m_buffer.seek(preWritePosition + m_buffer.bytesAvailable()); - m_buffer.write(data); - m_buffer.seek(preWritePosition); - while (!m_buffer.atEnd()) { - QString parseError; - BaseMessage::parse(&m_buffer, parseError, m_currentMessage); - qCDebug(LOGLSPCLIENTPARSE) << " complete: " << m_currentMessage.isComplete(); - qCDebug(LOGLSPCLIENTPARSE) << " length: " << m_currentMessage.contentLength; - qCDebug(LOGLSPCLIENTPARSE) << " content: " << m_currentMessage.content; - if (!parseError.isEmpty()) - log(parseError); - if (!m_currentMessage.isComplete()) - break; - if (auto handler = m_contentHandler[m_currentMessage.mimeType]){ - QString parseError; - handler(m_currentMessage.content, m_currentMessage.codec, parseError, - [this](MessageId id, const QByteArray &content, QTextCodec *codec){ - this->handleResponse(id, content, codec); - }, - [this](const QString &method, MessageId id, const IContent *content){ - this->handleMethod(method, id, content); - }); - if (!parseError.isEmpty()) - log(parseError); - } else { - log(tr("Cannot handle content of type: %1").arg(QLatin1String(m_currentMessage.mimeType))); - } - m_currentMessage = BaseMessage(); - } - if (m_buffer.atEnd()) { - m_buffer.close(); - m_buffer.setData(nullptr); - m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); - } -} - -StdIOClient::StdIOClient(const QString &executable, const QString &arguments) - : m_executable(executable) - , m_arguments(arguments) -{ - connect(&m_process, &QProcess::readyReadStandardError, - this, &StdIOClient::readError); - connect(&m_process, &QProcess::readyReadStandardOutput, - this, &StdIOClient::readOutput); - connect(&m_process, static_cast(&QProcess::finished), - this, &StdIOClient::onProcessFinished); - - m_process.setArguments(Utils::QtcProcess::splitArgs(m_arguments)); - m_process.setProgram(m_executable); -} - -StdIOClient::~StdIOClient() -{ - Utils::SynchronousProcess::stopProcess(m_process); -} - -bool StdIOClient::needsRestart(const StdIOSettings *settings) -{ - return m_executable != settings->m_executable || m_arguments != settings->m_arguments; -} - -bool StdIOClient::start() -{ - m_process.start(); - if (!m_process.waitForStarted() || m_process.state() != QProcess::Running) { - setError(m_process.errorString()); - return false; - } - return true; -} - -void StdIOClient::setWorkingDirectory(const QString &workingDirectory) -{ - m_process.setWorkingDirectory(workingDirectory); -} - -void StdIOClient::sendData(const QByteArray &data) -{ - if (m_process.state() != QProcess::Running) { - log(tr("Cannot send data to unstarted server %1").arg(m_process.program())); - return; - } - qCDebug(LOGLSPCLIENTV) << "StdIOClient send data:"; - qCDebug(LOGLSPCLIENTV).noquote() << data; - m_process.write(data); -} - -void StdIOClient::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - if (exitStatus == QProcess::CrashExit) - setError(tr("Crashed with exit code %1: %2").arg(exitCode, m_process.error())); - emit finished(); -} - -void StdIOClient::readError() -{ - qCDebug(LOGLSPCLIENTV) << "StdIOClient std err:\n"; - qCDebug(LOGLSPCLIENTV).noquote() << m_process.readAllStandardError(); -} - -void StdIOClient::readOutput() -{ - const QByteArray &out = m_process.readAllStandardOutput(); - qDebug(LOGLSPCLIENTV) << "StdIOClient std out:\n"; - qDebug(LOGLSPCLIENTV).noquote() << out; - parseData(out); -} - } // namespace LanguageClient diff --git a/src/plugins/languageclient/baseclient.h b/src/plugins/languageclient/client.h similarity index 84% rename from src/plugins/languageclient/baseclient.h rename to src/plugins/languageclient/client.h index 318d9ce67f9..6ae4166d2d1 100644 --- a/src/plugins/languageclient/baseclient.h +++ b/src/plugins/languageclient/client.h @@ -56,18 +56,20 @@ namespace TextEditor namespace LanguageClient { -class BaseClient : public QObject +class BaseClientInterface; + +class Client : public QObject { Q_OBJECT public: - BaseClient(); - ~BaseClient() override; + explicit Client(BaseClientInterface *clientInterface); // takes ownership + ~Client() override; - BaseClient(const BaseClient &) = delete; - BaseClient(BaseClient &&) = delete; - BaseClient &operator=(const BaseClient &) = delete; - BaseClient &operator=(BaseClient &&) = delete; + Client(const Client &) = delete; + Client(Client &&) = delete; + Client &operator=(const Client &) = delete; + Client &operator=(Client &&) = delete; enum State { Uninitialized, @@ -124,8 +126,8 @@ public: bool needsRestart(const BaseSettings *) const; - virtual bool start() { return true; } - virtual bool reset(); + bool start(); + bool reset(); void log(const QString &message, Core::MessageManager::PrintToOutputPaneFlag flag = Core::MessageManager::NoModeSwitch); @@ -143,8 +145,7 @@ signals: protected: void setError(const QString &message); - virtual void sendData(const QByteArray &data) = 0; - void parseData(const QByteArray &data); + void handleMessage(const LanguageServerProtocol::BaseMessage &message); private: void handleResponse(const LanguageServerProtocol::MessageId &id, const QByteArray &content, @@ -168,7 +169,6 @@ private: State m_state = Uninitialized; QHash m_responseHandlers; QHash m_contentHandler; - QBuffer m_buffer; QString m_displayName; LanguageFilter m_languagFilter; QList m_openedDocument; @@ -177,41 +177,9 @@ private: DynamicCapabilities m_dynamicCapabilities; LanguageClientCompletionAssistProvider m_completionProvider; QSet m_resetCompletionProvider; - LanguageServerProtocol::BaseMessage m_currentMessage; QHash m_highlightRequests; int m_restartsLeft = 5; -}; - -class StdIOClient : public BaseClient -{ - Q_OBJECT -public: - StdIOClient(const QString &executable, const QString &arguments); - ~StdIOClient() override; - - StdIOClient() = delete; - StdIOClient(const StdIOClient &) = delete; - StdIOClient(StdIOClient &&) = delete; - StdIOClient &operator=(const StdIOClient &) = delete; - StdIOClient &operator=(StdIOClient &&) = delete; - - bool needsRestart(const StdIOSettings *settings); - - bool start() override; - - void setWorkingDirectory(const QString &workingDirectory); - -protected: - void sendData(const QByteArray &data) final; - QProcess m_process; - -private: - void readError(); - void readOutput(); - void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); - - const QString m_executable; - const QString m_arguments; + QScopedPointer m_clientInterface; }; } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclient.pro b/src/plugins/languageclient/languageclient.pro index c9c821676f3..59c568a75b4 100644 --- a/src/plugins/languageclient/languageclient.pro +++ b/src/plugins/languageclient/languageclient.pro @@ -3,10 +3,11 @@ include(../../qtcreatorplugin.pri) DEFINES += LANGUAGECLIENT_LIBRARY HEADERS += \ - baseclient.h \ + client.h \ dynamiccapabilities.h \ languageclient_global.h \ languageclientcodeassist.h \ + languageclientinterface.h \ languageclientmanager.h \ languageclientoutline.h \ languageclientplugin.h \ @@ -15,9 +16,10 @@ HEADERS += \ SOURCES += \ - baseclient.cpp \ + client.cpp \ dynamiccapabilities.cpp \ languageclientcodeassist.cpp \ + languageclientinterface.cpp \ languageclientmanager.cpp \ languageclientoutline.cpp \ languageclientplugin.cpp \ diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs index 62de6c73092..0da6a580766 100644 --- a/src/plugins/languageclient/languageclient.qbs +++ b/src/plugins/languageclient/languageclient.qbs @@ -14,14 +14,16 @@ QtcPlugin { Depends { name: "TextEditor" } files: [ - "baseclient.cpp", - "baseclient.h", + "client.cpp", + "client.h", "dynamiccapabilities.cpp", "dynamiccapabilities.h", "languageclient.qrc", "languageclient_global.h", "languageclientcodeassist.cpp", "languageclientcodeassist.h", + "languageclientinterface.cpp", + "languageclientinterface.h", "languageclientmanager.cpp", "languageclientmanager.h", "languageclientoutline.cpp", diff --git a/src/plugins/languageclient/languageclientcodeassist.cpp b/src/plugins/languageclient/languageclientcodeassist.cpp index 42a4660f6e9..af0e9ce8920 100644 --- a/src/plugins/languageclient/languageclientcodeassist.cpp +++ b/src/plugins/languageclient/languageclientcodeassist.cpp @@ -25,7 +25,7 @@ #include "languageclientcodeassist.h" -#include "baseclient.h" +#include "client.h" #include "languageclientutils.h" #include @@ -48,10 +48,11 @@ static Q_LOGGING_CATEGORY(LOGLSPCOMPLETION, "qtc.languageclient.completion", QtWarningMsg); using namespace LanguageServerProtocol; +using namespace TextEditor; namespace LanguageClient { -class LanguageClientCompletionItem : public TextEditor::AssistProposalItemInterface +class LanguageClientCompletionItem : public AssistProposalItemInterface { public: LanguageClientCompletionItem(CompletionItem item); @@ -60,7 +61,7 @@ public: QString text() const override; bool implicitlyApplies() const override; bool prematurelyApplies(const QChar &typedCharacter) const override; - void apply(TextEditor::TextDocumentManipulatorInterface &manipulator, int basePosition) const override; + void apply(TextDocumentManipulatorInterface &manipulator, int basePosition) const override; QIcon icon() const override; QString detail() const override; bool isSnippet() const override; @@ -91,7 +92,7 @@ bool LanguageClientCompletionItem::implicitlyApplies() const bool LanguageClientCompletionItem::prematurelyApplies(const QChar &/*typedCharacter*/) const { return false; } -void LanguageClientCompletionItem::apply(TextEditor::TextDocumentManipulatorInterface &manipulator, +void LanguageClientCompletionItem::apply(TextDocumentManipulatorInterface &manipulator, int /*basePosition*/) const { const int pos = manipulator.currentPosition(); @@ -207,7 +208,7 @@ bool LanguageClientCompletionItem::isPerfectMatch(int pos, QTextDocument *doc) c return textToInsert == textAt(QTextCursor(doc), pos - length, length); } -class LanguageClientCompletionModel : public TextEditor::GenericProposalModel +class LanguageClientCompletionModel : public GenericProposalModel { public: // GenericProposalModel interface @@ -221,7 +222,6 @@ public: void LanguageClientCompletionModel::sort(const QString &/*prefix*/) { - using namespace TextEditor; std::sort(m_currentItems.begin(), m_currentItems.end(), [] (AssistProposalItemInterface *a, AssistProposalItemInterface *b){ return *(dynamic_cast(a)) < *( @@ -229,16 +229,16 @@ void LanguageClientCompletionModel::sort(const QString &/*prefix*/) }); } -class LanguageClientCompletionProposal : public TextEditor::GenericProposal +class LanguageClientCompletionProposal : public GenericProposal { public: LanguageClientCompletionProposal(int cursorPos, LanguageClientCompletionModel *model) - : TextEditor::GenericProposal(cursorPos, TextEditor::GenericProposalModelPtr(model)) + : GenericProposal(cursorPos, GenericProposalModelPtr(model)) , m_model(model) { } // IAssistProposal interface - bool hasItemsToPropose(const QString &/*text*/, TextEditor::AssistReason reason) const override + bool hasItemsToPropose(const QString &/*text*/, AssistReason reason) const override { if (m_model->size() <= 0 || m_document.isNull()) return false; @@ -255,11 +255,11 @@ public: }; -class LanguageClientCompletionAssistProcessor : public TextEditor::IAssistProcessor +class LanguageClientCompletionAssistProcessor : public IAssistProcessor { public: - LanguageClientCompletionAssistProcessor(BaseClient *client); - TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override; + LanguageClientCompletionAssistProcessor(Client *client); + IAssistProposal *perform(const AssistInterface *interface) override; bool running() override; bool needsRestart() const override { return true; } @@ -267,31 +267,30 @@ private: void handleCompletionResponse(const CompletionRequest::Response &response); QPointer m_document; - QPointer m_client; + QPointer m_client; bool m_running = false; int m_pos = -1; }; -LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(BaseClient *client) +LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(Client *client) : m_client(client) { } -static QString assistReasonString(TextEditor::AssistReason reason) +static QString assistReasonString(AssistReason reason) { switch (reason) { - case TextEditor::IdleEditor: return QString("idle editor"); - case TextEditor::ActivationCharacter: return QString("activation character"); - case TextEditor::ExplicitlyInvoked: return QString("explicitly invoking"); + case IdleEditor: return QString("idle editor"); + case ActivationCharacter: return QString("activation character"); + case ExplicitlyInvoked: return QString("explicitly invoking"); } return QString("unknown reason"); } -TextEditor::IAssistProposal *LanguageClientCompletionAssistProcessor::perform( - const TextEditor::AssistInterface *interface) +IAssistProposal *LanguageClientCompletionAssistProcessor::perform(const AssistInterface *interface) { QTC_ASSERT(m_client, return nullptr); m_pos = interface->position(); - if (interface->reason() == TextEditor::IdleEditor) { + if (interface->reason() == IdleEditor) { // Trigger an automatic completion request only when we are on a word with more than 2 "identifier" character const QRegExp regexp("[_a-zA-Z0-9]*"); int delta = 0; @@ -302,7 +301,7 @@ TextEditor::IAssistProposal *LanguageClientCompletionAssistProcessor::perform( } CompletionRequest completionRequest; CompletionParams::CompletionContext context; - context.setTriggerKind(interface->reason() == TextEditor::ActivationCharacter + context.setTriggerKind(interface->reason() == ActivationCharacter ? CompletionParams::TriggerCharacter : CompletionParams::Invoked); auto params = completionRequest.params().value_or(CompletionParams()); @@ -336,7 +335,6 @@ bool LanguageClientCompletionAssistProcessor::running() void LanguageClientCompletionAssistProcessor::handleCompletionResponse( const CompletionRequest::Response &response) { - using namespace TextEditor; qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : got completions"; m_running = false; QTC_ASSERT(m_client, return); @@ -369,18 +367,18 @@ void LanguageClientCompletionAssistProcessor::handleCompletionResponse( << items.count() << " completions handled"; } -LanguageClientCompletionAssistProvider::LanguageClientCompletionAssistProvider(BaseClient *client) +LanguageClientCompletionAssistProvider::LanguageClientCompletionAssistProvider(Client *client) : m_client(client) { } -TextEditor::IAssistProcessor *LanguageClientCompletionAssistProvider::createProcessor() const +IAssistProcessor *LanguageClientCompletionAssistProvider::createProcessor() const { return new LanguageClientCompletionAssistProcessor(m_client); } -TextEditor::IAssistProvider::RunType LanguageClientCompletionAssistProvider::runType() const +IAssistProvider::RunType LanguageClientCompletionAssistProvider::runType() const { - return TextEditor::IAssistProvider::Asynchronous; + return IAssistProvider::Asynchronous; } int LanguageClientCompletionAssistProvider::activationCharSequenceLength() const diff --git a/src/plugins/languageclient/languageclientcodeassist.h b/src/plugins/languageclient/languageclientcodeassist.h index 14af748e72b..1410e67f821 100644 --- a/src/plugins/languageclient/languageclientcodeassist.h +++ b/src/plugins/languageclient/languageclientcodeassist.h @@ -29,12 +29,12 @@ namespace LanguageClient { -class BaseClient; +class Client; class LanguageClientCompletionAssistProvider : public TextEditor::CompletionAssistProvider { public: - LanguageClientCompletionAssistProvider(BaseClient *client); + LanguageClientCompletionAssistProvider(Client *client); TextEditor::IAssistProcessor *createProcessor() const override; RunType runType() const override; @@ -48,7 +48,7 @@ public: private: QList m_triggerChars; int m_activationCharSequenceLength = 0; - BaseClient *m_client; + Client *m_client; }; } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientinterface.cpp b/src/plugins/languageclient/languageclientinterface.cpp new file mode 100644 index 00000000000..b67db95dde1 --- /dev/null +++ b/src/plugins/languageclient/languageclientinterface.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "languageclientinterface.h" + +#include "languageclientsettings.h" + +#include +#include + +#include + +using namespace LanguageServerProtocol; + +static Q_LOGGING_CATEGORY(LOGLSPCLIENTV, "qtc.languageclient.messages", QtWarningMsg); +static Q_LOGGING_CATEGORY(LOGLSPCLIENTPARSE, "qtc.languageclient.parse", QtWarningMsg); + +namespace LanguageClient { + +BaseClientInterface::BaseClientInterface() +{ + m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); +} + +BaseClientInterface::~BaseClientInterface() +{ + m_buffer.close(); +} + +void BaseClientInterface::sendMessage(const BaseMessage &message) +{ + sendData(message.toData()); +} + +void BaseClientInterface::resetBuffer() +{ + m_buffer.close(); + m_buffer.setData(nullptr); + m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); +} + +void BaseClientInterface::parseData(const QByteArray &data) +{ + const qint64 preWritePosition = m_buffer.pos(); + qCDebug(LOGLSPCLIENTPARSE) << "parse buffer pos: " << preWritePosition; + qCDebug(LOGLSPCLIENTPARSE) << " data: " << data; + if (!m_buffer.atEnd()) + m_buffer.seek(preWritePosition + m_buffer.bytesAvailable()); + m_buffer.write(data); + m_buffer.seek(preWritePosition); + while (!m_buffer.atEnd()) { + QString parseError; + BaseMessage::parse(&m_buffer, parseError, m_currentMessage); + qCDebug(LOGLSPCLIENTPARSE) << " complete: " << m_currentMessage.isComplete(); + qCDebug(LOGLSPCLIENTPARSE) << " length: " << m_currentMessage.contentLength; + qCDebug(LOGLSPCLIENTPARSE) << " content: " << m_currentMessage.content; + if (!parseError.isEmpty()) + emit error(parseError); + if (!m_currentMessage.isComplete()) + break; + emit messageReceived(m_currentMessage); + m_currentMessage = BaseMessage(); + } + if (m_buffer.atEnd()) { + m_buffer.close(); + m_buffer.setData(nullptr); + m_buffer.open(QIODevice::ReadWrite | QIODevice::Append); + } +} + +StdIOClientInterface::StdIOClientInterface(const QString &executable, const QString &arguments) + : m_executable(executable) + , m_arguments(arguments) +{ + connect(&m_process, &QProcess::readyReadStandardError, + this, &StdIOClientInterface::readError); + connect(&m_process, &QProcess::readyReadStandardOutput, + this, &StdIOClientInterface::readOutput); + connect(&m_process, QOverload::of(&QProcess::finished), + this, &StdIOClientInterface::onProcessFinished); + + m_process.setArguments(Utils::QtcProcess::splitArgs(m_arguments)); + m_process.setProgram(m_executable); +} + +StdIOClientInterface::~StdIOClientInterface() +{ + Utils::SynchronousProcess::stopProcess(m_process); +} + +bool StdIOClientInterface::needsRestart(const StdIOSettings *settings) +{ + return m_executable != settings->m_executable || m_arguments != settings->m_arguments; +} + +bool StdIOClientInterface::start() +{ + m_process.start(); + if (!m_process.waitForStarted() || m_process.state() != QProcess::Running) { + emit error(m_process.errorString()); + return false; + } + return true; +} + +void StdIOClientInterface::setWorkingDirectory(const QString &workingDirectory) +{ + m_process.setWorkingDirectory(workingDirectory); +} + +void StdIOClientInterface::sendData(const QByteArray &data) +{ + if (m_process.state() != QProcess::Running) { + emit error(tr("Cannot send data to unstarted server %1").arg(m_process.program())); + return; + } + qCDebug(LOGLSPCLIENTV) << "StdIOClient send data:"; + qCDebug(LOGLSPCLIENTV).noquote() << data; + m_process.write(data); +} + +void StdIOClientInterface::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitStatus == QProcess::CrashExit) + emit error(tr("Crashed with exit code %1: %2").arg(exitCode, m_process.error())); + emit finished(); +} + +void StdIOClientInterface::readError() +{ + qCDebug(LOGLSPCLIENTV) << "StdIOClient std err:\n"; + qCDebug(LOGLSPCLIENTV).noquote() << m_process.readAllStandardError(); +} + +void StdIOClientInterface::readOutput() +{ + const QByteArray &out = m_process.readAllStandardOutput(); + qCDebug(LOGLSPCLIENTV) << "StdIOClient std out:\n"; + qCDebug(LOGLSPCLIENTV).noquote() << out; + parseData(out); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientinterface.h b/src/plugins/languageclient/languageclientinterface.h new file mode 100644 index 00000000000..3dc2602d524 --- /dev/null +++ b/src/plugins/languageclient/languageclientinterface.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include +#include + +namespace LanguageClient { + +class StdIOSettings; + +class BaseClientInterface : public QObject +{ + Q_OBJECT +public: + BaseClientInterface(); + + virtual ~BaseClientInterface(); + + void sendMessage(const LanguageServerProtocol::BaseMessage &message); + virtual bool start() { return true; } + + void resetBuffer(); + +signals: + void messageReceived(LanguageServerProtocol::BaseMessage message); + void finished(); + void error(const QString &message); + +protected: + virtual void sendData(const QByteArray &data) = 0; + void parseData(const QByteArray &data); + +private: + QBuffer m_buffer; + LanguageServerProtocol::BaseMessage m_currentMessage; +}; + +class StdIOClientInterface : public BaseClientInterface +{ + Q_OBJECT +public: + StdIOClientInterface(const QString &executable, const QString &arguments); + ~StdIOClientInterface() override; + + StdIOClientInterface() = delete; + StdIOClientInterface(const StdIOClientInterface &) = delete; + StdIOClientInterface(StdIOClientInterface &&) = delete; + StdIOClientInterface &operator=(const StdIOClientInterface &) = delete; + StdIOClientInterface &operator=(StdIOClientInterface &&) = delete; + + bool needsRestart(const StdIOSettings *settings); + + bool start() override; + + void setWorkingDirectory(const QString &workingDirectory); + +protected: + void sendData(const QByteArray &data) final; + QProcess m_process; + +private: + void readError(); + void readOutput(); + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + + const QString m_executable; + const QString m_arguments; +}; + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index b4746b45ef4..3e0e25e66c5 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -109,7 +109,7 @@ void LanguageClientManager::init() void LanguageClientManager::publishDiagnostics(const Core::Id &id, const PublishDiagnosticsParams ¶ms, - BaseClient *publishingClient) + Client *publishingClient) { const Utils::FileName fileName = params.uri().toFileName(); TextEditor::TextDocument *doc = textDocumentForFileName(fileName); @@ -169,7 +169,7 @@ void LanguageClientManager::removeMarks(const Core::Id &id) removeMarks(fileName, id); } -void LanguageClientManager::startClient(BaseClient *client) +void LanguageClientManager::startClient(Client *client) { QTC_ASSERT(client, return); if (managerInstance->m_shuttingDown) { @@ -178,7 +178,7 @@ void LanguageClientManager::startClient(BaseClient *client) } if (!managerInstance->m_clients.contains(client)) managerInstance->m_clients.append(client); - connect(client, &BaseClient::finished, managerInstance, [client](){ + connect(client, &Client::finished, managerInstance, [client](){ managerInstance->clientFinished(client); }); if (client->start()) @@ -187,26 +187,26 @@ void LanguageClientManager::startClient(BaseClient *client) managerInstance->clientFinished(client); } -QVector LanguageClientManager::clients() +QVector LanguageClientManager::clients() { return managerInstance->m_clients; } -void LanguageClientManager::addExclusiveRequest(const MessageId &id, BaseClient *client) +void LanguageClientManager::addExclusiveRequest(const MessageId &id, Client *client) { managerInstance->m_exclusiveRequests[id] << client; } -void LanguageClientManager::reportFinished(const MessageId &id, BaseClient *byClient) +void LanguageClientManager::reportFinished(const MessageId &id, Client *byClient) { - for (BaseClient *client : managerInstance->m_exclusiveRequests[id]) { + for (Client *client : managerInstance->m_exclusiveRequests[id]) { if (client != byClient) client->cancelRequest(id); } managerInstance->m_exclusiveRequests.remove(id); } -void LanguageClientManager::deleteClient(BaseClient *client) +void LanguageClientManager::deleteClient(Client *client) { QTC_ASSERT(client, return); client->disconnect(); @@ -238,23 +238,23 @@ LanguageClientManager *LanguageClientManager::instance() return managerInstance; } -QList LanguageClientManager::clientsSupportingDocument( +QList LanguageClientManager::clientsSupportingDocument( const TextEditor::TextDocument *doc) { QTC_ASSERT(doc, return {};); - return Utils::filtered(managerInstance->reachableClients(), [doc](BaseClient *client) { + return Utils::filtered(managerInstance->reachableClients(), [doc](Client *client) { return client->isSupportedDocument(doc); }).toList(); } -QVector LanguageClientManager::reachableClients() +QVector LanguageClientManager::reachableClients() { - return Utils::filtered(m_clients, &BaseClient::reachable); + return Utils::filtered(m_clients, &Client::reachable); } -static void sendToInterfaces(const IContent &content, const QVector &interfaces) +static void sendToInterfaces(const IContent &content, const QVector &interfaces) { - for (BaseClient *interface : interfaces) + for (Client *interface : interfaces) interface->sendContent(content); } @@ -263,11 +263,11 @@ void LanguageClientManager::sendToAllReachableServers(const IContent &content) sendToInterfaces(content, reachableClients()); } -void LanguageClientManager::clientFinished(BaseClient *client) +void LanguageClientManager::clientFinished(Client *client) { constexpr int restartTimeoutS = 5; - const bool unexpectedFinish = client->state() != BaseClient::Shutdown - && client->state() != BaseClient::ShutdownRequested; + const bool unexpectedFinish = client->state() != Client::Shutdown + && client->state() != Client::ShutdownRequested; if (unexpectedFinish && !m_shuttingDown && client->reset()) { removeMarks(client->id()); client->disconnect(this); @@ -287,7 +287,7 @@ void LanguageClientManager::editorOpened(Core::IEditor *iEditor) { using namespace TextEditor; Core::IDocument *document = iEditor->document(); - for (BaseClient *interface : reachableClients()) + for (Client *interface : reachableClients()) interface->openDocument(document); if (auto textDocument = qobject_cast(document)) { @@ -315,7 +315,7 @@ void LanguageClientManager::editorsClosed(const QList &editors) removeMarks(editor->document()->filePath()); const DidCloseTextDocumentParams params(TextDocumentIdentifier( DocumentUri::fromFileName(editor->document()->filePath()))); - for (BaseClient *interface : reachableClients()) + for (Client *interface : reachableClients()) interface->closeDocument(params); } } @@ -323,13 +323,13 @@ void LanguageClientManager::editorsClosed(const QList &editors) void LanguageClientManager::documentContentsSaved(Core::IDocument *document) { - for (BaseClient *interface : reachableClients()) + for (Client *interface : reachableClients()) interface->documentContentsSaved(document); } void LanguageClientManager::documentWillSave(Core::IDocument *document) { - for (BaseClient *interface : reachableClients()) + for (Client *interface : reachableClients()) interface->documentContentsSaved(document); } @@ -355,7 +355,7 @@ void LanguageClientManager::findLinkAt(const Utils::FileName &filePath, } } }); - for (BaseClient *interface : reachableClients()) { + for (Client *interface : reachableClients()) { if (interface->findLinkAt(request)) m_exclusiveRequests[request.id()] << interface; } @@ -421,7 +421,7 @@ void LanguageClientManager::findUsages(const Utils::FileName &filePath, const QT search->popup(); } }; - for (BaseClient *client : reachableClients()) { + for (Client *client : reachableClients()) { request.setResponseCallback([callback, clientName = client->name()] (const FindReferencesRequest::Response &response){ callback(clientName, response); @@ -433,13 +433,13 @@ void LanguageClientManager::findUsages(const Utils::FileName &filePath, const QT void LanguageClientManager::projectAdded(ProjectExplorer::Project *project) { - for (BaseClient *interface : reachableClients()) + for (Client *interface : reachableClients()) interface->projectOpened(project); } void LanguageClientManager::projectRemoved(ProjectExplorer::Project *project) { - for (BaseClient *interface : reachableClients()) + for (Client *interface : reachableClients()) interface->projectClosed(project); } diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index 54f267d86d1..d062a0bdff9 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -25,7 +25,7 @@ #pragma once -#include "baseclient.h" +#include "client.h" #include "languageclientsettings.h" #include @@ -56,26 +56,26 @@ public: static void init(); static void publishDiagnostics(const Core::Id &id, - const LanguageServerProtocol::PublishDiagnosticsParams ¶ms, BaseClient *publishingClient); + const LanguageServerProtocol::PublishDiagnosticsParams ¶ms, Client *publishingClient); static void removeMark(LanguageClientMark *mark); static void removeMarks(const Utils::FileName &fileName); static void removeMarks(const Utils::FileName &fileName, const Core::Id &id); static void removeMarks(const Core::Id &id); - static void startClient(BaseClient *client); - static QVector clients(); + static void startClient(Client *client); + static QVector clients(); - static void addExclusiveRequest(const LanguageServerProtocol::MessageId &id, BaseClient *client); - static void reportFinished(const LanguageServerProtocol::MessageId &id, BaseClient *byClient); + static void addExclusiveRequest(const LanguageServerProtocol::MessageId &id, Client *client); + static void reportFinished(const LanguageServerProtocol::MessageId &id, Client *byClient); - static void deleteClient(BaseClient *client); + static void deleteClient(Client *client); static void shutdown(); static LanguageClientManager *instance(); - static QList clientsSupportingDocument(const TextEditor::TextDocument *doc); + static QList clientsSupportingDocument(const TextEditor::TextDocument *doc); signals: void shutdownFinished(); @@ -94,15 +94,15 @@ private: void projectAdded(ProjectExplorer::Project *project); void projectRemoved(ProjectExplorer::Project *project); - QVector reachableClients(); + QVector reachableClients(); void sendToAllReachableServers(const LanguageServerProtocol::IContent &content); - void clientFinished(BaseClient *client); + void clientFinished(Client *client); bool m_shuttingDown = false; - QVector m_clients; + QVector m_clients; QHash>> m_marks; - QHash> m_exclusiveRequests; + QHash> m_exclusiveRequests; friend class LanguageClientPlugin; }; diff --git a/src/plugins/languageclient/languageclientoutline.cpp b/src/plugins/languageclient/languageclientoutline.cpp index 841e9438ca0..3c3a1aa1b16 100644 --- a/src/plugins/languageclient/languageclientoutline.cpp +++ b/src/plugins/languageclient/languageclientoutline.cpp @@ -147,7 +147,7 @@ public: class LanguageClientOutlineWidget : public TextEditor::IOutlineWidget { public: - LanguageClientOutlineWidget(BaseClient *client, TextEditor::BaseTextEditor *editor); + LanguageClientOutlineWidget(Client *client, TextEditor::BaseTextEditor *editor); // IOutlineWidget interface public: @@ -160,14 +160,14 @@ private: void updateSelectionInTree(const QTextCursor ¤tCursor); void onItemActivated(const QModelIndex &index); - QPointer m_client; + QPointer m_client; QPointer m_editor; LanguageClientOutlineModel m_model; Utils::TreeView m_view; bool m_sync = false; }; -LanguageClientOutlineWidget::LanguageClientOutlineWidget(BaseClient *client, +LanguageClientOutlineWidget::LanguageClientOutlineWidget(Client *client, TextEditor::BaseTextEditor *editor) : m_client(client) , m_editor(editor) @@ -256,7 +256,7 @@ void LanguageClientOutlineWidget::onItemActivated(const QModelIndex &index) m_editor->widget()->setFocus(); } -static bool clientSupportsDocumentSymbols(const BaseClient *client, const TextEditor::TextDocument *doc) +static bool clientSupportsDocumentSymbols(const Client *client, const TextEditor::TextDocument *doc) { DynamicCapabilities dc = client->dynamicCapabilities(); if (dc.isRegistered(DocumentSymbolsRequest::methodName).value_or(false)) { @@ -273,7 +273,7 @@ bool LanguageClientOutlineWidgetFactory::supportsEditor(Core::IEditor *editor) c if (!doc) return false; auto clients = LanguageClientManager::clientsSupportingDocument(doc); - return Utils::anyOf(clients, [doc](const BaseClient *client){ + return Utils::anyOf(clients, [doc](const Client *client){ return clientSupportsDocumentSymbols(client, doc); }); } @@ -282,9 +282,9 @@ TextEditor::IOutlineWidget *LanguageClientOutlineWidgetFactory::createWidget(Cor { auto textEditor = qobject_cast(editor); QTC_ASSERT(textEditor, return nullptr); - QList clients = LanguageClientManager::clientsSupportingDocument(textEditor->textDocument()); + QList clients = LanguageClientManager::clientsSupportingDocument(textEditor->textDocument()); QTC_ASSERT(!clients.isEmpty(), return nullptr); - clients = Utils::filtered(clients, [doc = textEditor->textDocument()](const BaseClient *client){ + clients = Utils::filtered(clients, [doc = textEditor->textDocument()](const Client *client){ return clientSupportsDocumentSymbols(client, doc); }); return new LanguageClientOutlineWidget(clients.first(), textEditor); diff --git a/src/plugins/languageclient/languageclientplugin.cpp b/src/plugins/languageclient/languageclientplugin.cpp index 53a0252592a..1d64097e62b 100644 --- a/src/plugins/languageclient/languageclientplugin.cpp +++ b/src/plugins/languageclient/languageclientplugin.cpp @@ -25,7 +25,7 @@ #include "languageclientplugin.h" -#include "baseclient.h" +#include "client.h" namespace LanguageClient { diff --git a/src/plugins/languageclient/languageclientsettings.cpp b/src/plugins/languageclient/languageclientsettings.cpp index 36ec211b851..e101db412c7 100644 --- a/src/plugins/languageclient/languageclientsettings.cpp +++ b/src/plugins/languageclient/languageclientsettings.cpp @@ -25,9 +25,10 @@ #include "languageclientsettings.h" -#include "baseclient.h" +#include "client.h" #include "languageclientmanager.h" #include "languageclient_global.h" +#include "languageclientinterface.h" #include #include @@ -397,8 +398,15 @@ bool BaseSettings::isValid() const return !m_name.isEmpty(); } -BaseClient *BaseSettings::createClient() const +Client *BaseSettings::createClient() const { + BaseClientInterface *interface = createInterface(); + if (QTC_GUARD(interface)) { + auto *client = new Client(interface); + client->setName(m_name); + client->setSupportedLanguage(m_languageFilter); + return client; + } return nullptr; } @@ -467,8 +475,8 @@ bool StdIOSettings::needsRestart() const { if (BaseSettings::needsRestart()) return true; - if (auto stdIOClient = qobject_cast(m_client)) - return stdIOClient->needsRestart(this); + if (auto stdIOInterface = qobject_cast(m_client)) + return stdIOInterface->needsRestart(this); return false; } @@ -477,14 +485,6 @@ bool StdIOSettings::isValid() const return BaseSettings::isValid() && !m_executable.isEmpty(); } -BaseClient *StdIOSettings::createClient() const -{ - auto client = new StdIOClient(m_executable, m_arguments); - client->setName(m_name); - client->setSupportedLanguage(m_languageFilter); - return client; -} - QVariantMap StdIOSettings::toMap() const { QVariantMap map = BaseSettings::toMap(); @@ -500,6 +500,11 @@ void StdIOSettings::fromMap(const QVariantMap &map) m_arguments = map[argumentsKey].toString(); } +BaseClientInterface *StdIOSettings::createInterface() const +{ + return new StdIOClientInterface(m_executable, m_arguments); +} + BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *parent) : QWidget(parent) , m_name(new QLineEdit(settings->m_name, this)) diff --git a/src/plugins/languageclient/languageclientsettings.h b/src/plugins/languageclient/languageclientsettings.h index 54877412a70..bdf54e60525 100644 --- a/src/plugins/languageclient/languageclientsettings.h +++ b/src/plugins/languageclient/languageclientsettings.h @@ -43,7 +43,8 @@ namespace LanguageClient { constexpr char noLanguageFilter[] = "No Filter"; -class BaseClient; +class Client; +class BaseClientInterface; struct LanguageFilter { @@ -66,18 +67,20 @@ public: QString m_name = QString("New Language Server"); bool m_enabled = true; LanguageFilter m_languageFilter; - QPointer m_client; // not owned + QPointer m_client; // not owned virtual void applyFromSettingsWidget(QWidget *widget); virtual QWidget *createSettingsWidget(QWidget *parent = nullptr) const; virtual BaseSettings *copy() const { return new BaseSettings(*this); } virtual bool needsRestart() const; virtual bool isValid() const ; - virtual BaseClient *createClient() const; + Client *createClient() const; virtual QVariantMap toMap() const; virtual void fromMap(const QVariantMap &map); protected: + virtual BaseClientInterface *createInterface() const { return nullptr; } + BaseSettings(const BaseSettings &other) = default; BaseSettings(BaseSettings &&other) = default; BaseSettings &operator=(const BaseSettings &other) = default; @@ -105,11 +108,12 @@ public: BaseSettings *copy() const override { return new StdIOSettings(*this); } bool needsRestart() const override; bool isValid() const override; - BaseClient *createClient() const override; QVariantMap toMap() const override; void fromMap(const QVariantMap &map) override; protected: + BaseClientInterface *createInterface() const override; + StdIOSettings(const StdIOSettings &other) = default; StdIOSettings(StdIOSettings &&other) = default; StdIOSettings &operator=(const StdIOSettings &other) = default; diff --git a/src/plugins/languageclient/languageclientutils.cpp b/src/plugins/languageclient/languageclientutils.cpp index 3e0c884c178..5f3f8e06723 100644 --- a/src/plugins/languageclient/languageclientutils.cpp +++ b/src/plugins/languageclient/languageclientutils.cpp @@ -25,7 +25,7 @@ #include "languageclientutils.h" -#include "baseclient.h" +#include "client.h" #include @@ -131,7 +131,7 @@ QTextCursor endOfLineCursor(const QTextCursor &cursor) return ret; } -void updateCodeActionRefactoringMarker(BaseClient *client, +void updateCodeActionRefactoringMarker(Client *client, const CodeAction &action, const DocumentUri &uri) { @@ -176,7 +176,7 @@ void updateCodeActionRefactoringMarker(BaseClient *client, } } else if (action.command().has_value()) { const Command command = action.command().value(); - marker.callback = [command, client = QPointer(client)](const TextEditorWidget *) { + marker.callback = [command, client = QPointer(client)](const TextEditorWidget *) { if (client) client->executeCommand(command); }; diff --git a/src/plugins/languageclient/languageclientutils.h b/src/plugins/languageclient/languageclientutils.h index bf7ac602e43..2891c2d3037 100644 --- a/src/plugins/languageclient/languageclientutils.h +++ b/src/plugins/languageclient/languageclientutils.h @@ -37,7 +37,7 @@ class TextDocumentManipulatorInterface; namespace LanguageClient { -class BaseClient; +class Client; bool applyWorkspaceEdit(const LanguageServerProtocol::WorkspaceEdit &edit); bool applyTextDocumentEdit(const LanguageServerProtocol::TextDocumentEdit &edit); @@ -46,7 +46,7 @@ bool applyTextEdits(const LanguageServerProtocol::DocumentUri &uri, void applyTextEdit(TextEditor::TextDocumentManipulatorInterface &manipulator, const LanguageServerProtocol::TextEdit &edit); TextEditor::TextDocument *textDocumentForFileName(const Utils::FileName &fileName); -void updateCodeActionRefactoringMarker(BaseClient *client, +void updateCodeActionRefactoringMarker(Client *client, const LanguageServerProtocol::CodeAction &action, const LanguageServerProtocol::DocumentUri &uri); diff --git a/src/plugins/projectexplorer/buildtargetinfo.h b/src/plugins/projectexplorer/buildtargetinfo.h index 0aa5011dda1..38cadea9912 100644 --- a/src/plugins/projectexplorer/buildtargetinfo.h +++ b/src/plugins/projectexplorer/buildtargetinfo.h @@ -41,6 +41,7 @@ class PROJECTEXPLORER_EXPORT BuildTargetInfo public: QString buildKey; // Used to identify this BuildTargetInfo object in its list. QString displayName; + QString displayNameUniquifier; Utils::FileName targetFilePath; Utils::FileName projectFilePath; diff --git a/src/plugins/projectexplorer/runconfiguration.cpp b/src/plugins/projectexplorer/runconfiguration.cpp index 475eeb9dca3..2c8e2384955 100644 --- a/src/plugins/projectexplorer/runconfiguration.cpp +++ b/src/plugins/projectexplorer/runconfiguration.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include #include @@ -466,6 +467,7 @@ RunConfigurationFactory::availableCreators(Target *parent) const rci.id = m_runConfigBaseId; rci.buildKey = ti.buildKey; rci.displayName = displayName; + rci.displayNameUniquifier = ti.displayNameUniquifier; rci.creationMode = ti.isQtcRunnable || !hasAnyQtcRunnable ? RunConfigurationCreationInfo::AlwaysCreate : RunConfigurationCreationInfo::ManualCreationOnly; @@ -540,7 +542,7 @@ RunConfiguration *RunConfigurationCreationInfo::create(Target *target) const rc->m_buildKey = buildKey; rc->doAdditionalSetup(*this); - rc->setDefaultDisplayName(displayName); + rc->setDisplayName(displayName); return rc; } @@ -575,6 +577,15 @@ const QList RunConfigurationFactory::creatorsForTa if (factory->canHandle(parent)) items.append(factory->availableCreators(parent)); } + QHash> itemsPerDisplayName; + for (RunConfigurationCreationInfo &item : items) + itemsPerDisplayName[item.displayName] << &item; + for (auto it = itemsPerDisplayName.cbegin(); it != itemsPerDisplayName.cend(); ++it) { + if (it.value().size() == 1) + continue; + for (RunConfigurationCreationInfo * const rci : it.value()) + rci->displayName += rci->displayNameUniquifier; + } return items; } diff --git a/src/plugins/projectexplorer/runconfiguration.h b/src/plugins/projectexplorer/runconfiguration.h index c454f7e0ade..c0443ae07e2 100644 --- a/src/plugins/projectexplorer/runconfiguration.h +++ b/src/plugins/projectexplorer/runconfiguration.h @@ -234,6 +234,7 @@ public: Core::Id id; QString buildKey; QString displayName; + QString displayNameUniquifier; CreationMode creationMode = AlwaysCreate; bool useTerminal = false; }; diff --git a/src/plugins/pythoneditor/PythonEditor.json.in b/src/plugins/pythoneditor/PythonEditor.json.in index b4505d55465..89269328b3d 100644 --- a/src/plugins/pythoneditor/PythonEditor.json.in +++ b/src/plugins/pythoneditor/PythonEditor.json.in @@ -28,6 +28,7 @@ \" \", \" \", \" Qt Creator Python project file\", + \" \", \" \", \" \", \"\" diff --git a/src/plugins/pythoneditor/pythoneditorplugin.cpp b/src/plugins/pythoneditor/pythoneditorplugin.cpp index 64722ef7496..dfd9c6a50ea 100644 --- a/src/plugins/pythoneditor/pythoneditorplugin.cpp +++ b/src/plugins/pythoneditor/pythoneditorplugin.cpp @@ -59,6 +59,10 @@ #include #include #include +#include +#include +#include +#include using namespace Core; using namespace ProjectExplorer; @@ -340,6 +344,35 @@ static QStringList readLines(const Utils::FileName &projectFile) return lines; } +static QStringList readLinesJson(const Utils::FileName &projectFile) +{ + const QString projectFileName = projectFile.fileName(); + QStringList lines = { projectFileName }; + + QFile file(projectFile.toString()); + if (!file.open(QFile::ReadOnly)) + return lines; + const QByteArray content = file.readAll(); + + // This assumes te project file is formed with only one field called + // 'files' that has a list associated of the files to include in the project. + if (!content.isEmpty()) { + const QJsonDocument doc = QJsonDocument::fromJson(content); + const QJsonObject obj = doc.object(); + if (obj.contains("files")) { + QJsonValue files = obj.value("files"); + QJsonArray files_array = files.toArray(); + QSet visited; + for (const auto &file : files_array) + visited.insert(file.toString()); + + lines.append(visited.toList()); + } + } + + return lines; +} + bool PythonProject::saveRawFileList(const QStringList &rawFileList) { bool result = saveRawList(rawFileList, projectFilePath().toString()); @@ -418,7 +451,15 @@ bool PythonProject::renameFile(const QString &filePath, const QString &newFilePa void PythonProject::parseProject() { m_rawListEntries.clear(); - m_rawFileList = readLines(projectFilePath()); + const Utils::FileName filePath = projectFilePath(); + // The PySide project file is JSON based + if (filePath.endsWith(".pyproject")) + m_rawFileList = readLinesJson(filePath); + // To keep compatibility with PyQt we keep the compatibility with plain + // text files as project files. + else if (filePath.endsWith(".pyqtc")) + m_rawFileList = readLines(filePath); + m_files = processEntries(m_rawFileList, &m_rawListEntries); } @@ -449,7 +490,7 @@ void PythonProject::refresh(Target *target) auto newRoot = std::make_unique(this); for (const QString &f : m_files) { const QString displayName = baseDir.relativeFilePath(f); - FileType fileType = f.endsWith(".pyqtc") ? FileType::Project : FileType::Source; + FileType fileType = f.endsWith(".pyproject") || f.endsWith(".pyqtc") ? FileType::Project : FileType::Source; newRoot->addNestedNode(std::make_unique(FileName::fromString(f), displayName, fileType)); if (fileType == FileType::Source) { diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index 894163ab47c..6e92cf45d2c 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -1029,6 +1029,12 @@ void QmakeProject::updateBuildSystemData() bti.projectFilePath = node->filePath(); bti.workingDirectory = FileName::fromString(workingDir); bti.displayName = bti.projectFilePath.toFileInfo().completeBaseName(); + const FileName relativePathInProject + = bti.projectFilePath.relativeChildPath(projectDirectory()); + if (!relativePathInProject.isEmpty()) { + bti.displayNameUniquifier = QString::fromLatin1(" (%1)") + .arg(relativePathInProject.toUserOutput()); + } bti.buildKey = bti.projectFilePath.toString(); bti.isQtcRunnable = config.contains("qtc_runnable"); diff --git a/src/tools/icons/qtcreatoricons.svg b/src/tools/icons/qtcreatoricons.svg index 24f73e097e0..86fcbb48d00 100644 --- a/src/tools/icons/qtcreatoricons.svg +++ b/src/tools/icons/qtcreatoricons.svg @@ -2645,18 +2645,21 @@ id="path5662-0" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccccc" /> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/manual/debugger/python/README.md b/tests/manual/debugger/python/README.md index 0ed136ee403..31b9e70a18e 100644 --- a/tests/manual/debugger/python/README.md +++ b/tests/manual/debugger/python/README.md @@ -1,4 +1,4 @@ -- qtcreator -load PythonEditor ./python.pyqtc +- qtcreator -load PythonEditor ./python.pyproject (or ./python.pyqtc) - Switch active runconfiguration to main.py -