qmlls: call lsp uris urls

The Language Server Protocol calls several attributes "uri",
consistent with their definition in RFC 3986.
In Qml URI is normally used as module uri, i.e. a dot separated
sequence.
As per RFC 3986 URL is a subset of URI, but it turn out that the
usages in LSP are actually URLs, so use "url"" for its usages, to keep
the uri term as used in qqmlcodemodel unambiguous.
Leave the protocol objects usages of uri consistent with the LSP
definition, and use a noop lspUriToQmlUrl() to highlight the
transition between the two usages of uri.

Change-Id: Icf2ab5455e88849991e8a3432c5a2c91503d1285
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Fawzi Mohamed 2022-04-28 12:20:56 +02:00
parent 3a4b6b9ba2
commit e2708875ae
5 changed files with 78 additions and 66 deletions

View File

@ -123,19 +123,19 @@ QmlLintSuggestions::QmlLintSuggestions(QLanguageServer *server, QmlLsp::QQmlCode
&QmlLintSuggestions::diagnose, Qt::DirectConnection);
}
void QmlLintSuggestions::diagnose(const QByteArray &uri)
void QmlLintSuggestions::diagnose(const QByteArray &url)
{
const int maxInvalidMsec = 4000;
qCDebug(lintLog) << "diagnose start";
QmlLsp::OpenDocumentSnapshot snapshot = m_codeModel->snapshotByUri(uri);
QmlLsp::OpenDocumentSnapshot snapshot = m_codeModel->snapshotByUrl(url);
QList<Diagnostic> diagnostics;
std::optional<int> version;
DomItem doc;
{
QMutexLocker l(&m_mutex);
LastLintUpdate &lastUpdate = m_lastUpdate[uri];
LastLintUpdate &lastUpdate = m_lastUpdate[url];
if (lastUpdate.version && *lastUpdate.version == version) {
qCDebug(lspServerLog) << "skipped update of " << uri << "unchanged valid doc";
qCDebug(lspServerLog) << "skipped update of " << url << "unchanged valid doc";
return;
}
if (snapshot.validDocVersion
@ -153,7 +153,7 @@ void QmlLintSuggestions::diagnose(const QByteArray &uri)
} else if (!lastUpdate.invalidUpdatesSince) {
lastUpdate.invalidUpdatesSince = QDateTime::currentDateTime();
QTimer::singleShot(maxInvalidMsec, Qt::VeryCoarseTimer, this,
[this, uri]() { diagnose(uri); });
[this, url]() { diagnose(url); });
}
}
if (doc) {
@ -273,12 +273,12 @@ void QmlLintSuggestions::diagnose(const QByteArray &uri)
}
}
PublishDiagnosticsParams diagnosticParams;
diagnosticParams.uri = uri;
diagnosticParams.uri = url;
diagnosticParams.diagnostics = diagnostics;
diagnosticParams.version = version;
m_server->protocol()->notifyPublishDiagnostics(diagnosticParams);
qCDebug(lintLog) << "lint" << QString::fromUtf8(uri) << "found"
qCDebug(lintLog) << "lint" << QString::fromUtf8(url) << "found"
<< diagnosticParams.diagnostics.size() << "issues"
<< QTypedJson::toJsonValue(diagnosticParams);
}

View File

@ -64,11 +64,11 @@ synchronization here.
\section2 OpenFiles
\list
\li snapshotByUri() returns an OpenDocumentSnapshot of an open document. From it you can get the
\li snapshotByUrl() returns an OpenDocumentSnapshot of an open document. From it you can get the
document, its latest valid version, scope, all connected to a specific version of the document
and immutable. The signal updatedSnapshot() is called every time a snapshot changes (also for
every partial change: document change, validDocument change, scope change).
\li openDocumentByUri() is a lower level and more intrusive access to OpenDocument objects. These
\li openDocumentByUrl() is a lower level and more intrusive access to OpenDocument objects. These
contains the current snapshot, and shared pointer to a Utils::TextDocument. This is *always* the
current version of the document, and has line by line support.
Working on it is more delicate and intrusive, because you have to explicitly acquire its mutex()
@ -124,9 +124,9 @@ QQmlCodeModel::~QQmlCodeModel()
}
}
OpenDocumentSnapshot QQmlCodeModel::snapshotByUri(const QByteArray &uri)
OpenDocumentSnapshot QQmlCodeModel::snapshotByUrl(const QByteArray &url)
{
return openDocumentByUri(uri).snapshot;
return openDocumentByUrl(url).snapshot;
}
int QQmlCodeModel::indexEvalProgress() const
@ -272,49 +272,49 @@ void QQmlCodeModel::removeDirectory(const QString &path)
currentEnvPtr->removePath(path);
}
QString QQmlCodeModel::uri2Path(const QByteArray &uri, UriLookup options)
QString QQmlCodeModel::url2Path(const QByteArray &url, UrlLookup options)
{
QString res;
{
QMutexLocker l(&m_mutex);
res = m_uri2path.value(uri);
res = m_url2path.value(url);
}
if (!res.isEmpty() && options == UriLookup::Caching)
if (!res.isEmpty() && options == UrlLookup::Caching)
return res;
QUrl url(QString::fromUtf8(uri));
QFileInfo f(url.toLocalFile());
QUrl qurl(QString::fromUtf8(url));
QFileInfo f(qurl.toLocalFile());
QString cPath = f.canonicalFilePath();
if (cPath.isEmpty())
cPath = f.filePath();
{
QMutexLocker l(&m_mutex);
if (!res.isEmpty() && res != cPath)
m_path2uri.remove(res);
m_uri2path.insert(uri, cPath);
m_path2uri.insert(cPath, uri);
m_path2url.remove(res);
m_url2path.insert(url, cPath);
m_path2url.insert(cPath, url);
}
return cPath;
}
void QQmlCodeModel::newOpenFile(const QByteArray &uri, int version, const QString &docText)
void QQmlCodeModel::newOpenFile(const QByteArray &url, int version, const QString &docText)
{
{
QMutexLocker l(&m_mutex);
auto &openDoc = m_openDocuments[uri];
auto &openDoc = m_openDocuments[url];
if (!openDoc.textDocument)
openDoc.textDocument = std::make_shared<Utils::TextDocument>();
QMutexLocker l2(openDoc.textDocument->mutex());
openDoc.textDocument->setVersion(version);
openDoc.textDocument->setPlainText(docText);
}
addOpenToUpdate(uri);
addOpenToUpdate(url);
openNeedUpdate();
}
OpenDocument QQmlCodeModel::openDocumentByUri(const QByteArray &uri)
OpenDocument QQmlCodeModel::openDocumentByUrl(const QByteArray &url)
{
QMutexLocker l(&m_mutex);
return m_openDocuments.value(uri);
return m_openDocuments.value(url);
}
void QQmlCodeModel::indexNeedsUpdate()
@ -426,12 +426,12 @@ void QQmlCodeModel::openUpdateEnd()
qCDebug(codeModelLog) << "openUpdateEnd";
}
void QQmlCodeModel::newDocForOpenFile(const QByteArray &uri, int version, const QString &docText)
void QQmlCodeModel::newDocForOpenFile(const QByteArray &url, int version, const QString &docText)
{
qCDebug(codeModelLog) << "updating doc" << uri << "to version" << version << "("
qCDebug(codeModelLog) << "updating doc" << url << "to version" << version << "("
<< docText.length() << "chars)";
DomItem newCurrent = m_currentEnv.makeCopy(DomItem::CopyOption::EnvConnected).item();
QString fPath = uri2Path(uri, UriLookup::ForceLookup);
QString fPath = url2Path(url, UrlLookup::ForceLookup);
Path p;
newCurrent.loadFile(
fPath, fPath, docText, QDateTime::currentDateTimeUtc(),
@ -443,17 +443,17 @@ void QQmlCodeModel::newDocForOpenFile(const QByteArray &uri, int version, const
DomItem item = m_currentEnv.path(p);
{
QMutexLocker l(&m_mutex);
OpenDocument &doc = m_openDocuments[uri];
OpenDocument &doc = m_openDocuments[url];
if (!doc.textDocument) {
qCWarning(lspServerLog)
<< "ignoring update to closed document" << QString::fromUtf8(uri);
<< "ignoring update to closed document" << QString::fromUtf8(url);
return;
} else {
QMutexLocker l(doc.textDocument->mutex());
if (doc.textDocument->version() && *doc.textDocument->version() > version) {
qCWarning(lspServerLog)
<< "docUpdate: version" << version << "of document"
<< QString::fromUtf8(uri) << "is not the latest anymore";
<< QString::fromUtf8(url) << "is not the latest anymore";
return;
}
}
@ -461,8 +461,8 @@ void QQmlCodeModel::newDocForOpenFile(const QByteArray &uri, int version, const
doc.snapshot.docVersion = version;
doc.snapshot.doc = item;
} else {
qCWarning(lspServerLog) << "skippig update of current doc to obsolete version"
<< version << "of document" << QString::fromUtf8(uri);
qCWarning(lspServerLog) << "skipping update of current doc to obsolete version"
<< version << "of document" << QString::fromUtf8(url);
}
if (item.field(Fields::isValid).value().toBool(false)) {
if (!doc.snapshot.validDocVersion || *doc.snapshot.validDocVersion < version) {
@ -471,31 +471,31 @@ void QQmlCodeModel::newDocForOpenFile(const QByteArray &uri, int version, const
doc.snapshot.validDoc = vDoc;
} else {
qCWarning(lspServerLog) << "skippig update of valid doc to obsolete version"
<< version << "of document" << QString::fromUtf8(uri);
<< version << "of document" << QString::fromUtf8(url);
}
} else {
qCWarning(lspServerLog)
<< "avoid update of validDoc to " << version << "of document"
<< QString::fromUtf8(uri) << "as it is invalid";
<< QString::fromUtf8(url) << "as it is invalid";
}
}
}
if (codeModelLog().isDebugEnabled()) {
qCDebug(codeModelLog) << "finished update doc of " << uri << "to version" << version;
snapshotByUri(uri).dump(qDebug() << "postSnapshot",
qCDebug(codeModelLog) << "finished update doc of " << url << "to version" << version;
snapshotByUrl(url).dump(qDebug() << "postSnapshot",
OpenDocumentSnapshot::DumpOption::AllCode);
}
// we should update the scope in the future thus call addOpen(uri)
emit updatedSnapshot(uri);
// we should update the scope in the future thus call addOpen(url)
emit updatedSnapshot(url);
}
void QQmlCodeModel::closeOpenFile(const QByteArray &uri)
void QQmlCodeModel::closeOpenFile(const QByteArray &url)
{
QMutexLocker l(&m_mutex);
m_openDocuments.remove(uri);
m_openDocuments.remove(url);
}
void QQmlCodeModel::openUpdate(const QByteArray &uri)
void QQmlCodeModel::openUpdate(const QByteArray &url)
{
bool updateDoc = false;
bool updateScope = false;
@ -505,7 +505,7 @@ void QQmlCodeModel::openUpdate(const QByteArray &uri)
std::shared_ptr<Utils::TextDocument> document;
{
QMutexLocker l(&m_mutex);
OpenDocument &doc = m_openDocuments[uri];
OpenDocument &doc = m_openDocuments[url];
document = doc.textDocument;
if (!document)
return;
@ -531,23 +531,23 @@ void QQmlCodeModel::openUpdate(const QByteArray &uri)
}
}
if (updateDoc) {
newDocForOpenFile(uri, *rNow, docText);
newDocForOpenFile(url, *rNow, docText);
}
if (updateScope) {
// to do
}
}
void QQmlCodeModel::addOpenToUpdate(const QByteArray &uri)
void QQmlCodeModel::addOpenToUpdate(const QByteArray &url)
{
QMutexLocker l(&m_mutex);
m_openDocumentsToUpdate.insert(uri);
m_openDocumentsToUpdate.insert(url);
}
QDebug OpenDocumentSnapshot::dump(QDebug dbg, DumpOptions options)
{
dbg.noquote().nospace() << "{";
dbg << " uri:" << QString::fromUtf8(uri) << "\n";
dbg << " url:" << QString::fromUtf8(url) << "\n";
dbg << " docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) << "\n";
if (options & DumpOption::LatestCode) {
dbg << " doc: ------------\n"

View File

@ -52,7 +52,7 @@ public:
AllCode = LatestCode | ValidCode
};
Q_DECLARE_FLAGS(DumpOptions, DumpOption)
QByteArray uri;
QByteArray url;
std::optional<int> docVersion;
QQmlJS::Dom::DomItem doc;
std::optional<int> validDocVersion;
@ -83,15 +83,15 @@ class QQmlCodeModel : public QObject
{
Q_OBJECT
public:
enum class UriLookup { Caching, ForceLookup };
enum class UrlLookup { Caching, ForceLookup };
enum class State { Running, Stopping };
explicit QQmlCodeModel(QObject *parent = nullptr);
~QQmlCodeModel();
QQmlJS::Dom::DomItem currentEnv();
QQmlJS::Dom::DomItem validEnv();
OpenDocumentSnapshot snapshotByUri(const QByteArray &uri);
OpenDocument openDocumentByUri(const QByteArray &uri);
OpenDocumentSnapshot snapshotByUrl(const QByteArray &url);
OpenDocument openDocumentByUrl(const QByteArray &url);
void openNeedUpdate();
void indexNeedsUpdate();
@ -99,12 +99,12 @@ public:
void addOpenToUpdate(const QByteArray &);
void removeDirectory(const QString &path);
// void updateDocument(const OpenDocument &doc);
QString uri2Path(const QByteArray &uri, UriLookup options = UriLookup::Caching);
void newOpenFile(const QByteArray &uri, int version, const QString &docText);
void newDocForOpenFile(const QByteArray &uri, int version, const QString &docText);
void closeOpenFile(const QByteArray &uri);
QString url2Path(const QByteArray &url, UrlLookup options = UrlLookup::Caching);
void newOpenFile(const QByteArray &url, int version, const QString &docText);
void newDocForOpenFile(const QByteArray &url, int version, const QString &docText);
void closeOpenFile(const QByteArray &url);
signals:
void updatedSnapshot(const QByteArray &uri);
void updatedSnapshot(const QByteArray &url);
private:
void indexDirectory(const QString &path, int depthLeft);
int indexEvalProgress() const; // to be called in the mutex
@ -130,8 +130,8 @@ private:
QQmlJS::Dom::DomItem m_validEnv;
QByteArray m_lastOpenDocumentUpdated;
QSet<QByteArray> m_openDocumentsToUpdate;
QHash<QByteArray, QString> m_uri2path;
QHash<QString, QByteArray> m_path2uri;
QHash<QByteArray, QString> m_url2path;
QHash<QString, QByteArray> m_path2url;
QHash<QByteArray, OpenDocument> m_openDocuments;
};

View File

@ -36,6 +36,19 @@
QT_BEGIN_NAMESPACE
namespace QmlLsp {
/*
* The language server protocol calls "URI" what QML calls "URL".
* According to RFC 3986, a URL is a special case of URI that not only
* identifies a resource but also shows how to access it.
* In QML, however, URIs are distinct from URLs. URIs are the
* identifiers of modules, for example "QtQuick.Controls".
* In order to not confuse the terms we interpret language server URIs
* as URLs in the QML code model.
* This method marks a point of translation between the terms, but does
* not have to change the actual URI/URL.
*/
inline QByteArray lspUriToQmlUrl(const QByteArray &uri) { return uri; }
class QQmlLanguageServer : public QLanguageServerModule
{
Q_OBJECT

View File

@ -42,26 +42,25 @@ TextSynchronization::TextSynchronization(QmlLsp::QQmlCodeModel *codeModel, QObje
void TextSynchronization::didCloseTextDocument(const DidCloseTextDocumentParams &params)
{
m_codeModel->closeOpenFile(params.textDocument.uri);
m_codeModel->closeOpenFile(QmlLsp::lspUriToQmlUrl(params.textDocument.uri));
}
void TextSynchronization::didOpenTextDocument(const DidOpenTextDocumentParams &params)
{
const TextDocumentItem &item = params.textDocument;
const QString fileName = m_codeModel->uri2Path(item.uri);
m_codeModel->newOpenFile(item.uri, item.version, item.text);
const QString fileName = m_codeModel->url2Path(QmlLsp::lspUriToQmlUrl(item.uri));
m_codeModel->newOpenFile(QmlLsp::lspUriToQmlUrl(item.uri), item.version, item.text);
}
void TextSynchronization::didDidChangeTextDocument(const DidChangeTextDocumentParams &params)
{
QByteArray uri = params.textDocument.uri;
const QString fileName = m_codeModel->uri2Path(uri);
auto openDoc = m_codeModel->openDocumentByUri(uri);
QByteArray url = QmlLsp::lspUriToQmlUrl(params.textDocument.uri);
const QString fileName = m_codeModel->url2Path(url);
auto openDoc = m_codeModel->openDocumentByUrl(url);
std::shared_ptr<Utils::TextDocument> document = openDoc.textDocument;
if (!document) {
qCWarning(lspServerLog) << "Ingnoring changes to non open or closed document"
<< QString::fromUtf8(uri);
<< QString::fromUtf8(url);
return;
}
const auto &changes = params.contentChanges;
@ -86,7 +85,7 @@ void TextSynchronization::didDidChangeTextDocument(const DidChangeTextDocumentPa
}
document->setVersion(params.textDocument.version);
}
m_codeModel->addOpenToUpdate(uri);
m_codeModel->addOpenToUpdate(url);
m_codeModel->openNeedUpdate();
}