ifc-language-server/client/node_modules/vscode-languageclient/lib/common/diagnostic.js
Ryan Schultz 8afacf268a Implemented a working Language Server Protocol (LSP) for IFC files with:
- Hover provider showing entity information and type
- Go-to-definition (F12) for entity references
- Basic IFC file validation (ISO-10303-21 header check)
- Entity parsing with regex-based detection
- Proper CommonJS module system (avoiding ES module issues)

This replaces the broken baseline from ifc-developer-tools which had:
- Non-functional ES module configuration
- Circular dependency issues
- Parser crashes
- Non-working PositionVisitor

Built on Microsoft's LSP example template for a clean, maintainable foundation.

Next: Add hierarchical entity dependency tree in hover tooltip."
2025-12-07 10:20:07 -06:00

814 lines
37 KiB
JavaScript

"use strict";
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DiagnosticFeature = exports.DiagnosticPullMode = exports.vsdiag = void 0;
const minimatch = require("minimatch");
const vscode_1 = require("vscode");
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const uuid_1 = require("./utils/uuid");
const features_1 = require("./features");
function ensure(target, key) {
if (target[key] === void 0) {
target[key] = {};
}
return target[key];
}
var vsdiag;
(function (vsdiag) {
let DocumentDiagnosticReportKind;
(function (DocumentDiagnosticReportKind) {
DocumentDiagnosticReportKind["full"] = "full";
DocumentDiagnosticReportKind["unChanged"] = "unChanged";
})(DocumentDiagnosticReportKind = vsdiag.DocumentDiagnosticReportKind || (vsdiag.DocumentDiagnosticReportKind = {}));
})(vsdiag || (exports.vsdiag = vsdiag = {}));
var DiagnosticPullMode;
(function (DiagnosticPullMode) {
DiagnosticPullMode["onType"] = "onType";
DiagnosticPullMode["onSave"] = "onSave";
})(DiagnosticPullMode || (exports.DiagnosticPullMode = DiagnosticPullMode = {}));
var RequestStateKind;
(function (RequestStateKind) {
RequestStateKind["active"] = "open";
RequestStateKind["reschedule"] = "reschedule";
RequestStateKind["outDated"] = "drop";
})(RequestStateKind || (RequestStateKind = {}));
/**
* Manages the open tabs. We don't directly use the tab API since for
* diagnostics we need to de-dupe tabs that show the same resources since
* we pull on the model not the UI.
*/
class Tabs {
constructor() {
this.open = new Set();
this._onOpen = new vscode_1.EventEmitter();
this._onClose = new vscode_1.EventEmitter();
Tabs.fillTabResources(this.open);
const openTabsHandler = (event) => {
if (event.closed.length === 0 && event.opened.length === 0) {
return;
}
const oldTabs = this.open;
const currentTabs = new Set();
Tabs.fillTabResources(currentTabs);
const closed = new Set();
const opened = new Set(currentTabs);
for (const tab of oldTabs.values()) {
if (currentTabs.has(tab)) {
opened.delete(tab);
}
else {
closed.add(tab);
}
}
this.open = currentTabs;
if (closed.size > 0) {
const toFire = new Set();
for (const item of closed) {
toFire.add(vscode_1.Uri.parse(item));
}
this._onClose.fire(toFire);
}
if (opened.size > 0) {
const toFire = new Set();
for (const item of opened) {
toFire.add(vscode_1.Uri.parse(item));
}
this._onOpen.fire(toFire);
}
};
if (vscode_1.window.tabGroups.onDidChangeTabs !== undefined) {
this.disposable = vscode_1.window.tabGroups.onDidChangeTabs(openTabsHandler);
}
else {
this.disposable = { dispose: () => { } };
}
}
get onClose() {
return this._onClose.event;
}
get onOpen() {
return this._onOpen.event;
}
dispose() {
this.disposable.dispose();
}
isActive(document) {
return document instanceof vscode_1.Uri
? vscode_1.window.activeTextEditor?.document.uri === document
: vscode_1.window.activeTextEditor?.document === document;
}
isVisible(document) {
const uri = document instanceof vscode_1.Uri ? document : document.uri;
return this.open.has(uri.toString());
}
getTabResources() {
const result = new Set();
Tabs.fillTabResources(new Set(), result);
return result;
}
static fillTabResources(strings, uris) {
const seen = strings ?? new Set();
for (const group of vscode_1.window.tabGroups.all) {
for (const tab of group.tabs) {
const input = tab.input;
let uri;
if (input instanceof vscode_1.TabInputText) {
uri = input.uri;
}
else if (input instanceof vscode_1.TabInputTextDiff) {
uri = input.modified;
}
else if (input instanceof vscode_1.TabInputCustom) {
uri = input.uri;
}
if (uri !== undefined && !seen.has(uri.toString())) {
seen.add(uri.toString());
uris !== undefined && uris.add(uri);
}
}
}
}
}
var PullState;
(function (PullState) {
PullState[PullState["document"] = 1] = "document";
PullState[PullState["workspace"] = 2] = "workspace";
})(PullState || (PullState = {}));
var DocumentOrUri;
(function (DocumentOrUri) {
function asKey(document) {
return document instanceof vscode_1.Uri ? document.toString() : document.uri.toString();
}
DocumentOrUri.asKey = asKey;
})(DocumentOrUri || (DocumentOrUri = {}));
class DocumentPullStateTracker {
constructor() {
this.documentPullStates = new Map();
this.workspacePullStates = new Map();
}
track(kind, document, arg1) {
const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
const [key, uri, version] = document instanceof vscode_1.Uri
? [document.toString(), document, arg1]
: [document.uri.toString(), document.uri, document.version];
let state = states.get(key);
if (state === undefined) {
state = { document: uri, pulledVersion: version, resultId: undefined };
states.set(key, state);
}
return state;
}
update(kind, document, arg1, arg2) {
const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
const [key, uri, version, resultId] = document instanceof vscode_1.Uri
? [document.toString(), document, arg1, arg2]
: [document.uri.toString(), document.uri, document.version, arg1];
let state = states.get(key);
if (state === undefined) {
state = { document: uri, pulledVersion: version, resultId };
states.set(key, state);
}
else {
state.pulledVersion = version;
state.resultId = resultId;
}
}
unTrack(kind, document) {
const key = DocumentOrUri.asKey(document);
const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
states.delete(key);
}
tracks(kind, document) {
const key = DocumentOrUri.asKey(document);
const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
return states.has(key);
}
getResultId(kind, document) {
const key = DocumentOrUri.asKey(document);
const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
return states.get(key)?.resultId;
}
getAllResultIds() {
const result = [];
for (let [uri, value] of this.workspacePullStates) {
if (this.documentPullStates.has(uri)) {
value = this.documentPullStates.get(uri);
}
if (value.resultId !== undefined) {
result.push({ uri, value: value.resultId });
}
}
return result;
}
}
class DiagnosticRequestor {
constructor(client, tabs, options) {
this.client = client;
this.tabs = tabs;
this.options = options;
this.isDisposed = false;
this.onDidChangeDiagnosticsEmitter = new vscode_1.EventEmitter();
this.provider = this.createProvider();
this.diagnostics = vscode_1.languages.createDiagnosticCollection(options.identifier);
this.openRequests = new Map();
this.documentStates = new DocumentPullStateTracker();
this.workspaceErrorCounter = 0;
}
knows(kind, document) {
const uri = document instanceof vscode_1.Uri ? document : document.uri;
return this.documentStates.tracks(kind, document) || this.openRequests.has(uri.toString());
}
forget(kind, document) {
this.documentStates.unTrack(kind, document);
}
pull(document, cb) {
if (this.isDisposed) {
return;
}
const uri = document instanceof vscode_1.Uri ? document : document.uri;
this.pullAsync(document).then(() => {
if (cb) {
cb();
}
}, (error) => {
this.client.error(`Document pull failed for text document ${uri.toString()}`, error, false);
});
}
async pullAsync(document, version) {
if (this.isDisposed) {
return;
}
const isUri = document instanceof vscode_1.Uri;
const uri = isUri ? document : document.uri;
const key = uri.toString();
version = isUri ? version : document.version;
const currentRequestState = this.openRequests.get(key);
const documentState = isUri
? this.documentStates.track(PullState.document, document, version)
: this.documentStates.track(PullState.document, document);
if (currentRequestState === undefined) {
const tokenSource = new vscode_1.CancellationTokenSource();
this.openRequests.set(key, { state: RequestStateKind.active, document: document, version: version, tokenSource });
let report;
let afterState;
try {
report = await this.provider.provideDiagnostics(document, documentState.resultId, tokenSource.token) ?? { kind: vsdiag.DocumentDiagnosticReportKind.full, items: [] };
}
catch (error) {
if (error instanceof features_1.LSPCancellationError && vscode_languageserver_protocol_1.DiagnosticServerCancellationData.is(error.data) && error.data.retriggerRequest === false) {
afterState = { state: RequestStateKind.outDated, document };
}
if (afterState === undefined && error instanceof vscode_1.CancellationError) {
afterState = { state: RequestStateKind.reschedule, document };
}
else {
throw error;
}
}
afterState = afterState ?? this.openRequests.get(key);
if (afterState === undefined) {
// This shouldn't happen. Log it
this.client.error(`Lost request state in diagnostic pull model. Clearing diagnostics for ${key}`);
this.diagnostics.delete(uri);
return;
}
this.openRequests.delete(key);
if (!this.tabs.isVisible(document)) {
this.documentStates.unTrack(PullState.document, document);
return;
}
if (afterState.state === RequestStateKind.outDated) {
return;
}
// report is only undefined if the request has thrown.
if (report !== undefined) {
if (report.kind === vsdiag.DocumentDiagnosticReportKind.full) {
this.diagnostics.set(uri, report.items);
}
documentState.pulledVersion = version;
documentState.resultId = report.resultId;
}
if (afterState.state === RequestStateKind.reschedule) {
this.pull(document);
}
}
else {
if (currentRequestState.state === RequestStateKind.active) {
// Cancel the current request and reschedule a new one when the old one returned.
currentRequestState.tokenSource.cancel();
this.openRequests.set(key, { state: RequestStateKind.reschedule, document: currentRequestState.document });
}
else if (currentRequestState.state === RequestStateKind.outDated) {
this.openRequests.set(key, { state: RequestStateKind.reschedule, document: currentRequestState.document });
}
}
}
forgetDocument(document) {
const uri = document instanceof vscode_1.Uri ? document : document.uri;
const key = uri.toString();
const request = this.openRequests.get(key);
if (this.options.workspaceDiagnostics) {
// If we run workspace diagnostic pull a last time for the diagnostics
// and the rely on getting them from the workspace result.
if (request !== undefined) {
this.openRequests.set(key, { state: RequestStateKind.reschedule, document: document });
}
else {
this.pull(document, () => {
this.forget(PullState.document, document);
});
}
}
else {
// We have normal pull or inter file dependencies. In this case we
// clear the diagnostics (to have the same start as after startup).
// We also cancel outstanding requests.
if (request !== undefined) {
if (request.state === RequestStateKind.active) {
request.tokenSource.cancel();
}
this.openRequests.set(key, { state: RequestStateKind.outDated, document: document });
}
this.diagnostics.delete(uri);
this.forget(PullState.document, document);
}
}
pullWorkspace() {
if (this.isDisposed) {
return;
}
this.pullWorkspaceAsync().then(() => {
this.workspaceTimeout = (0, vscode_languageserver_protocol_1.RAL)().timer.setTimeout(() => {
this.pullWorkspace();
}, 2000);
}, (error) => {
if (!(error instanceof features_1.LSPCancellationError) && !vscode_languageserver_protocol_1.DiagnosticServerCancellationData.is(error.data)) {
this.client.error(`Workspace diagnostic pull failed.`, error, false);
this.workspaceErrorCounter++;
}
if (this.workspaceErrorCounter <= 5) {
this.workspaceTimeout = (0, vscode_languageserver_protocol_1.RAL)().timer.setTimeout(() => {
this.pullWorkspace();
}, 2000);
}
});
}
async pullWorkspaceAsync() {
if (!this.provider.provideWorkspaceDiagnostics || this.isDisposed) {
return;
}
if (this.workspaceCancellation !== undefined) {
this.workspaceCancellation.cancel();
this.workspaceCancellation = undefined;
}
this.workspaceCancellation = new vscode_1.CancellationTokenSource();
const previousResultIds = this.documentStates.getAllResultIds().map((item) => {
return {
uri: this.client.protocol2CodeConverter.asUri(item.uri),
value: item.value
};
});
await this.provider.provideWorkspaceDiagnostics(previousResultIds, this.workspaceCancellation.token, (chunk) => {
if (!chunk || this.isDisposed) {
return;
}
for (const item of chunk.items) {
if (item.kind === vsdiag.DocumentDiagnosticReportKind.full) {
// Favour document pull result over workspace results. So skip if it is tracked
// as a document result.
if (!this.documentStates.tracks(PullState.document, item.uri)) {
this.diagnostics.set(item.uri, item.items);
}
}
this.documentStates.update(PullState.workspace, item.uri, item.version ?? undefined, item.resultId);
}
});
}
createProvider() {
const result = {
onDidChangeDiagnostics: this.onDidChangeDiagnosticsEmitter.event,
provideDiagnostics: (document, previousResultId, token) => {
const provideDiagnostics = (document, previousResultId, token) => {
const params = {
identifier: this.options.identifier,
textDocument: { uri: this.client.code2ProtocolConverter.asUri(document instanceof vscode_1.Uri ? document : document.uri) },
previousResultId: previousResultId
};
if (this.isDisposed === true || !this.client.isRunning()) {
return { kind: vsdiag.DocumentDiagnosticReportKind.full, items: [] };
}
return this.client.sendRequest(vscode_languageserver_protocol_1.DocumentDiagnosticRequest.type, params, token).then(async (result) => {
if (result === undefined || result === null || this.isDisposed || token.isCancellationRequested) {
return { kind: vsdiag.DocumentDiagnosticReportKind.full, items: [] };
}
if (result.kind === vscode_languageserver_protocol_1.DocumentDiagnosticReportKind.Full) {
return { kind: vsdiag.DocumentDiagnosticReportKind.full, resultId: result.resultId, items: await this.client.protocol2CodeConverter.asDiagnostics(result.items, token) };
}
else {
return { kind: vsdiag.DocumentDiagnosticReportKind.unChanged, resultId: result.resultId };
}
}, (error) => {
return this.client.handleFailedRequest(vscode_languageserver_protocol_1.DocumentDiagnosticRequest.type, token, error, { kind: vsdiag.DocumentDiagnosticReportKind.full, items: [] });
});
};
const middleware = this.client.middleware;
return middleware.provideDiagnostics
? middleware.provideDiagnostics(document, previousResultId, token, provideDiagnostics)
: provideDiagnostics(document, previousResultId, token);
}
};
if (this.options.workspaceDiagnostics) {
result.provideWorkspaceDiagnostics = (resultIds, token, resultReporter) => {
const convertReport = async (report) => {
if (report.kind === vscode_languageserver_protocol_1.DocumentDiagnosticReportKind.Full) {
return {
kind: vsdiag.DocumentDiagnosticReportKind.full,
uri: this.client.protocol2CodeConverter.asUri(report.uri),
resultId: report.resultId,
version: report.version,
items: await this.client.protocol2CodeConverter.asDiagnostics(report.items, token)
};
}
else {
return {
kind: vsdiag.DocumentDiagnosticReportKind.unChanged,
uri: this.client.protocol2CodeConverter.asUri(report.uri),
resultId: report.resultId,
version: report.version
};
}
};
const convertPreviousResultIds = (resultIds) => {
const converted = [];
for (const item of resultIds) {
converted.push({ uri: this.client.code2ProtocolConverter.asUri(item.uri), value: item.value });
}
return converted;
};
const provideDiagnostics = (resultIds, token) => {
const partialResultToken = (0, uuid_1.generateUuid)();
const disposable = this.client.onProgress(vscode_languageserver_protocol_1.WorkspaceDiagnosticRequest.partialResult, partialResultToken, async (partialResult) => {
if (partialResult === undefined || partialResult === null) {
resultReporter(null);
return;
}
const converted = {
items: []
};
for (const item of partialResult.items) {
try {
converted.items.push(await convertReport(item));
}
catch (error) {
this.client.error(`Converting workspace diagnostics failed.`, error);
}
}
resultReporter(converted);
});
const params = {
identifier: this.options.identifier,
previousResultIds: convertPreviousResultIds(resultIds),
partialResultToken: partialResultToken
};
if (this.isDisposed === true || !this.client.isRunning()) {
return { items: [] };
}
return this.client.sendRequest(vscode_languageserver_protocol_1.WorkspaceDiagnosticRequest.type, params, token).then(async (result) => {
if (token.isCancellationRequested) {
return { items: [] };
}
const converted = {
items: []
};
for (const item of result.items) {
converted.items.push(await convertReport(item));
}
disposable.dispose();
resultReporter(converted);
return { items: [] };
}, (error) => {
disposable.dispose();
return this.client.handleFailedRequest(vscode_languageserver_protocol_1.DocumentDiagnosticRequest.type, token, error, { items: [] });
});
};
const middleware = this.client.middleware;
return middleware.provideWorkspaceDiagnostics
? middleware.provideWorkspaceDiagnostics(resultIds, token, resultReporter, provideDiagnostics)
: provideDiagnostics(resultIds, token, resultReporter);
};
}
return result;
}
dispose() {
this.isDisposed = true;
// Cancel and clear workspace pull if present.
this.workspaceCancellation?.cancel();
this.workspaceTimeout?.dispose();
// Cancel all request and mark open requests as outdated.
for (const [key, request] of this.openRequests) {
if (request.state === RequestStateKind.active) {
request.tokenSource.cancel();
}
this.openRequests.set(key, { state: RequestStateKind.outDated, document: request.document });
}
// cleanup old diagnostics
this.diagnostics.dispose();
}
}
class BackgroundScheduler {
constructor(diagnosticRequestor) {
this.diagnosticRequestor = diagnosticRequestor;
this.documents = new vscode_languageserver_protocol_1.LinkedMap();
this.isDisposed = false;
}
add(document) {
if (this.isDisposed === true) {
return;
}
const key = DocumentOrUri.asKey(document);
if (this.documents.has(key)) {
return;
}
this.documents.set(key, document, vscode_languageserver_protocol_1.Touch.Last);
this.trigger();
}
remove(document) {
const key = DocumentOrUri.asKey(document);
this.documents.delete(key);
// No more documents. Stop background activity.
if (this.documents.size === 0) {
this.stop();
}
else if (key === this.endDocumentKey()) {
// Make sure we have a correct last document. It could have
this.endDocument = this.documents.last;
}
}
trigger() {
if (this.isDisposed === true) {
return;
}
// We have a round running. So simply make sure we run up to the
// last document
if (this.intervalHandle !== undefined) {
this.endDocument = this.documents.last;
return;
}
this.endDocument = this.documents.last;
this.intervalHandle = (0, vscode_languageserver_protocol_1.RAL)().timer.setInterval(() => {
const document = this.documents.first;
if (document !== undefined) {
const key = DocumentOrUri.asKey(document);
this.diagnosticRequestor.pull(document);
this.documents.set(key, document, vscode_languageserver_protocol_1.Touch.Last);
if (key === this.endDocumentKey()) {
this.stop();
}
}
}, 200);
}
dispose() {
this.isDisposed = true;
this.stop();
this.documents.clear();
}
stop() {
this.intervalHandle?.dispose();
this.intervalHandle = undefined;
this.endDocument = undefined;
}
endDocumentKey() {
return this.endDocument !== undefined ? DocumentOrUri.asKey(this.endDocument) : undefined;
}
}
class DiagnosticFeatureProviderImpl {
constructor(client, tabs, options) {
const diagnosticPullOptions = client.clientOptions.diagnosticPullOptions ?? { onChange: true, onSave: false };
const documentSelector = client.protocol2CodeConverter.asDocumentSelector(options.documentSelector);
const disposables = [];
const matchResource = (resource) => {
const selector = options.documentSelector;
if (diagnosticPullOptions.match !== undefined) {
return diagnosticPullOptions.match(selector, resource);
}
for (const filter of selector) {
if (!vscode_languageserver_protocol_1.TextDocumentFilter.is(filter)) {
continue;
}
// The filter is a language id. We can't determine if it matches
// so we return false.
if (typeof filter === 'string') {
return false;
}
if (filter.language !== undefined && filter.language !== '*') {
return false;
}
if (filter.scheme !== undefined && filter.scheme !== '*' && filter.scheme !== resource.scheme) {
return false;
}
if (filter.pattern !== undefined) {
const matcher = new minimatch.Minimatch(filter.pattern, { noext: true });
if (!matcher.makeRe()) {
return false;
}
if (!matcher.match(resource.fsPath)) {
return false;
}
}
}
return true;
};
const matches = (document) => {
return document instanceof vscode_1.Uri
? matchResource(document)
: vscode_1.languages.match(documentSelector, document) > 0 && tabs.isVisible(document);
};
const isActiveDocument = (document) => {
return document instanceof vscode_1.Uri
? this.activeTextDocument?.uri.toString() === document.toString()
: this.activeTextDocument === document;
};
this.diagnosticRequestor = new DiagnosticRequestor(client, tabs, options);
this.backgroundScheduler = new BackgroundScheduler(this.diagnosticRequestor);
const addToBackgroundIfNeeded = (document) => {
if (!matches(document) || !options.interFileDependencies || isActiveDocument(document)) {
return;
}
this.backgroundScheduler.add(document);
};
this.activeTextDocument = vscode_1.window.activeTextEditor?.document;
vscode_1.window.onDidChangeActiveTextEditor((editor) => {
const oldActive = this.activeTextDocument;
this.activeTextDocument = editor?.document;
if (oldActive !== undefined) {
addToBackgroundIfNeeded(oldActive);
}
if (this.activeTextDocument !== undefined) {
this.backgroundScheduler.remove(this.activeTextDocument);
}
});
// For pull model diagnostics we pull for documents visible in the UI.
// From an eventing point of view we still rely on open document events
// and filter the documents that are not visible in the UI instead of
// listening to Tab events. Major reason is event timing since we need
// to ensure that the pull is send after the document open has reached
// the server.
// We always pull on open.
const openFeature = client.getFeature(vscode_languageserver_protocol_1.DidOpenTextDocumentNotification.method);
disposables.push(openFeature.onNotificationSent((event) => {
const textDocument = event.textDocument;
// We already know about this document. This can happen via a tab open.
if (this.diagnosticRequestor.knows(PullState.document, textDocument)) {
return;
}
if (matches(textDocument)) {
this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); });
}
}));
disposables.push(tabs.onOpen((opened) => {
for (const resource of opened) {
// We already know about this document. This can happen via a document open.
if (this.diagnosticRequestor.knows(PullState.document, resource)) {
continue;
}
const uriStr = resource.toString();
let textDocument;
for (const item of vscode_1.workspace.textDocuments) {
if (uriStr === item.uri.toString()) {
textDocument = item;
break;
}
}
// In VS Code the event timing is as follows:
// 1. tab events are fired.
// 2. open document events are fired and internal data structures like
// workspace.textDocuments and Window.activeTextEditor are updated.
//
// This means: for newly created tab/editors we don't find the underlying
// document yet. So we do nothing an rely on the underlying open document event
// to be fired.
if (textDocument !== undefined && matches(textDocument)) {
this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); });
}
}
}));
// Pull all diagnostics for documents that are already open
const pulledTextDocuments = new Set();
for (const textDocument of vscode_1.workspace.textDocuments) {
if (matches(textDocument)) {
this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); });
pulledTextDocuments.add(textDocument.uri.toString());
}
}
// Pull all tabs if not already pulled as text document
if (diagnosticPullOptions.onTabs === true) {
for (const resource of tabs.getTabResources()) {
if (!pulledTextDocuments.has(resource.toString()) && matches(resource)) {
this.diagnosticRequestor.pull(resource, () => { addToBackgroundIfNeeded(resource); });
}
}
}
// We don't need to pull on tab open since we will receive a document open as well later on
// and that event allows us to use a document for a match check which will have a set
// language id.
if (diagnosticPullOptions.onChange === true) {
const changeFeature = client.getFeature(vscode_languageserver_protocol_1.DidChangeTextDocumentNotification.method);
disposables.push(changeFeature.onNotificationSent(async (event) => {
const textDocument = event.textDocument;
if ((diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, DiagnosticPullMode.onType)) && this.diagnosticRequestor.knows(PullState.document, textDocument)) {
this.diagnosticRequestor.pull(textDocument, () => { this.backgroundScheduler.trigger(); });
}
}));
}
if (diagnosticPullOptions.onSave === true) {
const saveFeature = client.getFeature(vscode_languageserver_protocol_1.DidSaveTextDocumentNotification.method);
disposables.push(saveFeature.onNotificationSent((event) => {
const textDocument = event.textDocument;
if ((diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, DiagnosticPullMode.onSave)) && this.diagnosticRequestor.knows(PullState.document, textDocument)) {
this.diagnosticRequestor.pull(event.textDocument, () => { this.backgroundScheduler.trigger(); });
}
}));
}
// When the document closes clear things up
const closeFeature = client.getFeature(vscode_languageserver_protocol_1.DidCloseTextDocumentNotification.method);
disposables.push(closeFeature.onNotificationSent((event) => {
this.cleanUpDocument(event.textDocument);
}));
// Same when a tabs closes.
tabs.onClose((closed) => {
for (const document of closed) {
this.cleanUpDocument(document);
}
});
// We received a did change from the server.
this.diagnosticRequestor.onDidChangeDiagnosticsEmitter.event(() => {
for (const textDocument of vscode_1.workspace.textDocuments) {
if (matches(textDocument)) {
this.diagnosticRequestor.pull(textDocument);
}
}
});
// da348dc5-c30a-4515-9d98-31ff3be38d14 is the test UUID to test the middle ware. So don't auto trigger pulls.
if (options.workspaceDiagnostics === true && options.identifier !== 'da348dc5-c30a-4515-9d98-31ff3be38d14') {
this.diagnosticRequestor.pullWorkspace();
}
this.disposable = vscode_1.Disposable.from(...disposables, this.backgroundScheduler, this.diagnosticRequestor);
}
get onDidChangeDiagnosticsEmitter() {
return this.diagnosticRequestor.onDidChangeDiagnosticsEmitter;
}
get diagnostics() {
return this.diagnosticRequestor.provider;
}
cleanUpDocument(document) {
if (this.diagnosticRequestor.knows(PullState.document, document)) {
this.diagnosticRequestor.forgetDocument(document);
this.backgroundScheduler.remove(document);
}
}
}
class DiagnosticFeature extends features_1.TextDocumentLanguageFeature {
constructor(client) {
super(client, vscode_languageserver_protocol_1.DocumentDiagnosticRequest.type);
}
fillClientCapabilities(capabilities) {
let capability = ensure(ensure(capabilities, 'textDocument'), 'diagnostic');
capability.dynamicRegistration = true;
// We first need to decide how a UI will look with related documents.
// An easy implementation would be to only show related diagnostics for
// the active editor.
capability.relatedDocumentSupport = false;
ensure(ensure(capabilities, 'workspace'), 'diagnostics').refreshSupport = true;
}
initialize(capabilities, documentSelector) {
const client = this._client;
client.onRequest(vscode_languageserver_protocol_1.DiagnosticRefreshRequest.type, async () => {
for (const provider of this.getAllProviders()) {
provider.onDidChangeDiagnosticsEmitter.fire();
}
});
let [id, options] = this.getRegistration(documentSelector, capabilities.diagnosticProvider);
if (!id || !options) {
return;
}
this.register({ id: id, registerOptions: options });
}
clear() {
if (this.tabs !== undefined) {
this.tabs.dispose();
this.tabs = undefined;
}
super.clear();
}
registerLanguageProvider(options) {
if (this.tabs === undefined) {
this.tabs = new Tabs();
}
const provider = new DiagnosticFeatureProviderImpl(this._client, this.tabs, options);
return [provider.disposable, provider];
}
}
exports.DiagnosticFeature = DiagnosticFeature;